aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGerald Barker <geraldbarker@gmail.com>2011-09-02 23:19:51 +0100
committerGerald Barker <geraldbarker@gmail.com>2011-09-02 23:19:51 +0100
commit53936c7ee6654a525075b03e1b4c8ddbfbcd4ef5 (patch)
tree41154be6daecad1b4483223d72920e0a290024ef /src
parentafbe8c5164e1157e38e0f8c4b28942cbd9fe3d41 (diff)
downloadcgeo-53936c7ee6654a525075b03e1b4c8ddbfbcd4ef5.zip
cgeo-53936c7ee6654a525075b03e1b4c8ddbfbcd4ef5.tar.gz
cgeo-53936c7ee6654a525075b03e1b4c8ddbfbcd4ef5.tar.bz2
First commit of Apache Commons String & Date functions and required, and
refactor of code to use them.
Diffstat (limited to 'src')
-rw-r--r--src/cgeo/geocaching/LogTemplateProvider.java4
-rw-r--r--src/cgeo/geocaching/StaticMapsProvider.java6
-rw-r--r--src/cgeo/geocaching/activity/ActivityMixin.java12
-rw-r--r--src/cgeo/geocaching/apps/AbstractApp.java3
-rw-r--r--src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java4
-rw-r--r--src/cgeo/geocaching/apps/cache/navi/LocusApp.java24
-rw-r--r--src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java4
-rw-r--r--src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java4
-rw-r--r--src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java8
-rw-r--r--src/cgeo/geocaching/cgBase.java230
-rw-r--r--src/cgeo/geocaching/cgCache.java40
-rw-r--r--src/cgeo/geocaching/cgCacheListAdapter.java11
-rw-r--r--src/cgeo/geocaching/cgData.java79
-rw-r--r--src/cgeo/geocaching/cgDirectionImg.java5
-rw-r--r--src/cgeo/geocaching/cgGeo.java4
-rw-r--r--src/cgeo/geocaching/cgHtmlImg.java5
-rw-r--r--src/cgeo/geocaching/cgSettings.java14
-rw-r--r--src/cgeo/geocaching/cgWaypoint.java10
-rw-r--r--src/cgeo/geocaching/cgeo.java7
-rw-r--r--src/cgeo/geocaching/cgeoadvsearch.java16
-rw-r--r--src/cgeo/geocaching/cgeoapplication.java16
-rw-r--r--src/cgeo/geocaching/cgeoauth.java10
-rw-r--r--src/cgeo/geocaching/cgeocaches.java29
-rw-r--r--src/cgeo/geocaching/cgeocoords.java4
-rw-r--r--src/cgeo/geocaching/cgeodetail.java73
-rw-r--r--src/cgeo/geocaching/cgeoinit.java12
-rw-r--r--src/cgeo/geocaching/cgeonavigate.java16
-rw-r--r--src/cgeo/geocaching/cgeopoint.java10
-rw-r--r--src/cgeo/geocaching/cgeopopup.java10
-rw-r--r--src/cgeo/geocaching/cgeosmaps.java3
-rw-r--r--src/cgeo/geocaching/cgeotouch.java11
-rw-r--r--src/cgeo/geocaching/cgeotrackable.java49
-rw-r--r--src/cgeo/geocaching/cgeovisit.java41
-rw-r--r--src/cgeo/geocaching/cgeowaypoint.java6
-rw-r--r--src/cgeo/geocaching/cgeowaypointadd.java12
-rw-r--r--src/cgeo/geocaching/connector/GCConnector.java4
-rw-r--r--src/cgeo/geocaching/connector/OCConnector.java4
-rw-r--r--src/cgeo/geocaching/connector/OXConnector.java4
-rw-r--r--src/cgeo/geocaching/files/FileList.java4
-rw-r--r--src/cgeo/geocaching/files/GPXParser.java19
-rw-r--r--src/cgeo/geocaching/files/LocParser.java10
-rw-r--r--src/cgeo/geocaching/mapcommon/cgUsersOverlay.java4
-rw-r--r--src/cgeo/geocaching/mapcommon/cgeomap.java7
-rw-r--r--src/cgeo/geocaching/sorting/GeocodeComparator.java6
-rw-r--r--src/cgeo/geocaching/sorting/NameComparator.java4
-rw-r--r--src/cgeo/geocaching/sorting/SizeComparator.java4
-rw-r--r--src/cgeo/geocaching/utils/CollectionUtils.java25
-rw-r--r--src/org/apache/commons/lang3/ArrayUtils.java5796
-rw-r--r--src/org/apache/commons/lang3/CharSequenceUtils.java197
-rw-r--r--src/org/apache/commons/lang3/CharUtils.java539
-rw-r--r--src/org/apache/commons/lang3/ClassUtils.java1103
-rw-r--r--src/org/apache/commons/lang3/JavaVersion.java168
-rw-r--r--src/org/apache/commons/lang3/ObjectUtils.java608
-rw-r--r--src/org/apache/commons/lang3/StringUtils.java6557
-rw-r--r--src/org/apache/commons/lang3/SystemUtils.java1419
-rw-r--r--src/org/apache/commons/lang3/Validate.java1071
-rw-r--r--src/org/apache/commons/lang3/builder/Builder.java89
-rw-r--r--src/org/apache/commons/lang3/builder/CompareToBuilder.java1019
-rw-r--r--src/org/apache/commons/lang3/builder/EqualsBuilder.java944
-rw-r--r--src/org/apache/commons/lang3/builder/HashCodeBuilder.java961
-rw-r--r--src/org/apache/commons/lang3/builder/IDKey.java74
-rw-r--r--src/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java697
-rw-r--r--src/org/apache/commons/lang3/builder/StandardToStringStyle.java560
-rw-r--r--src/org/apache/commons/lang3/builder/ToStringBuilder.java1079
-rw-r--r--src/org/apache/commons/lang3/builder/ToStringStyle.java2271
-rw-r--r--src/org/apache/commons/lang3/exception/CloneFailedException.java62
-rw-r--r--src/org/apache/commons/lang3/exception/ContextedException.java246
-rw-r--r--src/org/apache/commons/lang3/exception/ContextedRuntimeException.java247
-rw-r--r--src/org/apache/commons/lang3/exception/DefaultExceptionContext.java158
-rw-r--r--src/org/apache/commons/lang3/exception/ExceptionContext.java103
-rw-r--r--src/org/apache/commons/lang3/exception/ExceptionUtils.java697
-rw-r--r--src/org/apache/commons/lang3/mutable/Mutable.java54
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableBoolean.java193
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableByte.java283
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableDouble.java312
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableFloat.java313
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableInt.java273
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableLong.java273
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableObject.java126
-rw-r--r--src/org/apache/commons/lang3/mutable/MutableShort.java283
-rw-r--r--src/org/apache/commons/lang3/time/DateFormatUtils.java320
-rw-r--r--src/org/apache/commons/lang3/time/DateUtils.java1831
-rw-r--r--src/org/apache/commons/lang3/time/DurationFormatUtils.java662
-rw-r--r--src/org/apache/commons/lang3/time/FastDateFormat.java1519
-rw-r--r--src/org/apache/commons/lang3/time/FormatCache.java202
-rw-r--r--src/org/apache/commons/lang3/time/StopWatch.java382
-rw-r--r--src/org/apache/commons/lang3/tuple/ImmutablePair.java103
-rw-r--r--src/org/apache/commons/lang3/tuple/MutablePair.java123
-rw-r--r--src/org/apache/commons/lang3/tuple/Pair.java176
89 files changed, 34585 insertions, 395 deletions
diff --git a/src/cgeo/geocaching/LogTemplateProvider.java b/src/cgeo/geocaching/LogTemplateProvider.java
index 2edf7c0..60c7cc7 100644
--- a/src/cgeo/geocaching/LogTemplateProvider.java
+++ b/src/cgeo/geocaching/LogTemplateProvider.java
@@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.util.Log;
@@ -110,7 +112,7 @@ public class LogTemplateProvider {
}
private static int parseFindCount(String page) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
return -1;
}
diff --git a/src/cgeo/geocaching/StaticMapsProvider.java b/src/cgeo/geocaching/StaticMapsProvider.java
index b649f7b..cac5118 100644
--- a/src/cgeo/geocaching/StaticMapsProvider.java
+++ b/src/cgeo/geocaching/StaticMapsProvider.java
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -18,6 +19,7 @@ import android.content.Context;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
+import cgeo.geocaching.utils.CollectionUtils;
public class StaticMapsProvider {
private static final String MARKERS_URL = "http://cgeo.carnero.cc/_markers/";
@@ -122,7 +124,7 @@ public class StaticMapsProvider {
public static void downloadMaps(cgCache cache, cgSettings settings, Activity activity) {
if (settings.storeOfflineMaps != 1 || cache.latitude == null
- || cache.longitude == null || cache.geocode == null || cache.geocode.length() == 0) {
+ || cache.longitude == null || StringUtils.isNotBlank(cache.geocode)) {
return;
}
@@ -138,7 +140,7 @@ public class StaticMapsProvider {
}
final StringBuilder waypoints = new StringBuilder();
- if (cache.waypoints != null && cache.waypoints.size() > 0) {
+ if (CollectionUtils.isNotEmpty(cache.waypoints)) {
for (cgWaypoint waypoint : cache.waypoints) {
if (waypoint.latitude == null && waypoint.longitude == null) {
continue;
diff --git a/src/cgeo/geocaching/activity/ActivityMixin.java b/src/cgeo/geocaching/activity/ActivityMixin.java
index acbb405..5c76c08 100644
--- a/src/cgeo/geocaching/activity/ActivityMixin.java
+++ b/src/cgeo/geocaching/activity/ActivityMixin.java
@@ -4,6 +4,8 @@ import gnu.android.app.appmanualclient.AppManualReaderClient;
import java.util.ArrayList;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -35,7 +37,7 @@ public final class ActivityMixin {
}
public final static void goManual(final Context context, final String helpTopic) {
- if (helpTopic == null || helpTopic.length() == 0) {
+ if (StringUtils.isBlank(helpTopic)) {
return;
}
try {
@@ -50,7 +52,7 @@ public final class ActivityMixin {
}
public final static void setTitle(final Activity activity, final String text) {
- if (text == null) {
+ if (StringUtils.isBlank(text)) {
return;
}
@@ -83,7 +85,7 @@ public final class ActivityMixin {
}
public final static void showToast(final Activity activity, final String text) {
- if (text.length() > 0) {
+ if (StringUtils.isNotBlank(text)) {
Toast toast = Toast.makeText(activity, text, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM, 0, 100);
@@ -92,7 +94,7 @@ public final class ActivityMixin {
}
public final static void showShortToast(final Activity activity, final String text) {
- if (text.length() > 0) {
+ if (StringUtils.isNotBlank(text)) {
Toast toast = Toast.makeText(activity, text, Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM, 0, 100);
@@ -101,7 +103,7 @@ public final class ActivityMixin {
}
public static final void helpDialog(final Activity activity, final String title, final String message) {
- if (message == null || message.length() == 0) {
+ if (StringUtils.isBlank(message)) {
return;
}
diff --git a/src/cgeo/geocaching/apps/AbstractApp.java b/src/cgeo/geocaching/apps/AbstractApp.java
index ba8333c..a6b22f8 100644
--- a/src/cgeo/geocaching/apps/AbstractApp.java
+++ b/src/cgeo/geocaching/apps/AbstractApp.java
@@ -8,6 +8,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import cgeo.geocaching.cgSettings;
+import cgeo.geocaching.utils.CollectionUtils;
public abstract class AbstractApp implements App {
@@ -54,7 +55,7 @@ public abstract class AbstractApp implements App {
final List<ResolveInfo> list = packageManager.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY);
- return (list.size() > 0);
+ return (CollectionUtils.isNotEmpty(list));
}
@Override
diff --git a/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java b/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java
index 8291365..c184bd0 100644
--- a/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java
+++ b/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.apps.cache;
+import org.apache.commons.lang3.ArrayUtils;
+
import android.app.Activity;
import android.content.res.Resources;
import android.util.Log;
@@ -13,7 +15,7 @@ public final class GeneralAppsFactory extends AbstractAppFactory {
private static GeneralApp[] apps = new GeneralApp[] {};
private static GeneralApp[] getGeneralApps(Resources res) {
- if (null == apps || 0 == apps.length) {
+ if (ArrayUtils.isEmpty(apps)) {
apps = new GeneralApp[] { new GccApp(res),
new WhereYouGoApp(res) };
}
diff --git a/src/cgeo/geocaching/apps/cache/navi/LocusApp.java b/src/cgeo/geocaching/apps/cache/navi/LocusApp.java
index 2b4baf4..208ff93 100644
--- a/src/cgeo/geocaching/apps/cache/navi/LocusApp.java
+++ b/src/cgeo/geocaching/apps/cache/navi/LocusApp.java
@@ -4,6 +4,8 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -15,6 +17,7 @@ import cgeo.geocaching.cgCache;
import cgeo.geocaching.cgGeo;
import cgeo.geocaching.cgWaypoint;
import cgeo.geocaching.apps.AbstractLocusApp;
+import cgeo.geocaching.utils.CollectionUtils;
class LocusApp extends AbstractLocusApp implements NavigationApp {
@@ -83,22 +86,18 @@ class LocusApp extends AbstractLocusApp implements NavigationApp {
}
// name
- if (cache != null && cache.name != null
- && cache.name.length() > 0) {
+ if (cache != null && StringUtils.isNotBlank(cache.name)) {
dos.writeUTF(cache.name);
- } else if (waypoint != null && waypoint.name != null
- && waypoint.name.length() > 0) {
+ } else if (waypoint != null && StringUtils.isNotBlank(waypoint.name)) {
dos.writeUTF(waypoint.name);
} else {
dos.writeUTF("");
}
// description
- if (cache != null && cache.geocode != null
- && cache.geocode.length() > 0) {
+ if (cache != null && StringUtils.isNotBlank(cache.geocode)) {
dos.writeUTF(cache.geocode.toUpperCase());
- } else if (waypoint != null && waypoint.lookup != null
- && waypoint.lookup.length() > 0) {
+ } else if (waypoint != null && StringUtils.isNotBlank(waypoint.lookup)) {
dos.writeUTF(waypoint.lookup.toUpperCase());
} else {
dos.writeUTF("");
@@ -106,8 +105,7 @@ class LocusApp extends AbstractLocusApp implements NavigationApp {
// additional data :: keyword, button title, package, activity,
// data name, data content
- if (cache != null && cache.geocode != null
- && cache.geocode.length() > 0) {
+ if (cache != null && StringUtils.isNotBlank(cache.geocode)) {
dos.writeUTF("intent;c:geo;cgeo.geocaching;cgeo.geocaching.cgeodetail;geocode;"
+ cache.geocode);
} else if (waypoint != null && waypoint.id != null
@@ -132,7 +130,7 @@ class LocusApp extends AbstractLocusApp implements NavigationApp {
}
// cache waypoints
- if (waypoints != null && waypoints.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(waypoints)) {
for (cgWaypoint wp : waypoints) {
if (wp == null || wp.latitude == null
|| wp.longitude == null) {
@@ -159,14 +157,14 @@ class LocusApp extends AbstractLocusApp implements NavigationApp {
}
// name
- if (wp.lookup != null && wp.lookup.length() > 0) {
+ if (StringUtils.isNotBlank(wp.lookup)) {
dos.writeUTF(wp.lookup.toUpperCase());
} else {
dos.writeUTF("");
}
// description
- if (wp.name != null && wp.name.length() > 0) {
+ if (StringUtils.isNotBlank(wp.name)) {
dos.writeUTF(wp.name);
} else {
dos.writeUTF("");
diff --git a/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
index e496e33..0bb13da 100644
--- a/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
+++ b/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
@@ -2,6 +2,8 @@ package cgeo.geocaching.apps.cache.navi;
import java.util.ArrayList;
+import org.apache.commons.lang3.ArrayUtils;
+
import android.app.Activity;
import android.content.res.Resources;
import android.util.Log;
@@ -17,7 +19,7 @@ public final class NavigationAppFactory extends AbstractAppFactory {
private static NavigationApp[] apps = new NavigationApp[] {};
private static NavigationApp[] getNavigationApps(Resources res) {
- if (null == apps || 0 == apps.length) {
+ if (ArrayUtils.isEmpty(apps)) {
apps = new NavigationApp[] {
// compass
new RadarApp(res),
diff --git a/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java b/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java
index 416388d..1b85583 100644
--- a/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java
+++ b/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java
@@ -3,6 +3,8 @@ package cgeo.geocaching.apps.cachelist;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+
import android.app.Activity;
import android.content.res.Resources;
import android.util.Log;
@@ -20,7 +22,7 @@ public final class CacheListAppFactory extends AbstractAppFactory {
private static CacheListApp[] getMultiPointNavigationApps(
Resources res) {
- if (null == apps || 0 == apps.length) {
+ if (ArrayUtils.isEmpty(apps)) {
apps = new CacheListApp[] {
new InternalCacheListMap(res),
new LocusCacheListApp(res) };
diff --git a/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java b/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java
index 0e03821..4e97d6d 100644
--- a/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java
+++ b/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java
@@ -5,6 +5,8 @@ import java.io.DataOutputStream;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -61,21 +63,21 @@ class LocusCacheListApp extends AbstractLocusApp implements CacheListApp {
}
// name
- if (cache.geocode != null && cache.geocode.length() > 0) {
+ if (StringUtils.isNotBlank(cache.geocode)) {
dos.writeUTF(cache.geocode.toUpperCase());
} else {
dos.writeUTF("");
}
// description
- if (cache.name != null && cache.name.length() > 0) {
+ if (StringUtils.isNotBlank(cache.name)) {
dos.writeUTF(cache.name);
} else {
dos.writeUTF("");
}
// additional data :: keyword, button title, package, activity, data name, data content
- if (cache.geocode != null && cache.geocode.length() > 0) {
+ if (StringUtils.isNotBlank(cache.geocode)) {
dos.writeUTF("intent;c:geo;cgeo.geocaching;cgeo.geocaching.cgeodetail;geocode;" + cache.geocode);
} else {
dos.writeUTF("");
diff --git a/src/cgeo/geocaching/cgBase.java b/src/cgeo/geocaching/cgBase.java
index cfa1a07..1e0891e 100644
--- a/src/cgeo/geocaching/cgBase.java
+++ b/src/cgeo/geocaching/cgBase.java
@@ -46,6 +46,8 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -67,6 +69,7 @@ import android.util.Log;
import android.widget.EditText;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.files.LocParser;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgBase {
@@ -444,7 +447,7 @@ public class cgBase {
* put viewstates into request parameters
*/
private static void setViewstates(String[] viewstates, HashMap<String, String> params) {
- if (viewstates == null || viewstates.length == 0)
+ if (ArrayUtils.isEmpty(viewstates))
return;
params.put("__VIEWSTATE", viewstates[0]);
if (viewstates.length > 1) {
@@ -462,21 +465,6 @@ public class cgBase {
setViewstates(getViewstates(page), params);
}
- /**
- * checks if an Array of Strings is empty or not. Empty means:
- * - Array is null
- * - or all elements are null or empty strings
- */
- public static boolean isEmpty(String[] a) {
- if (a == null)
- return true;
-
- for (String s: a)
- if (s != null && s.length() > 0)
- return false;
-
- return true;
- }
public class loginThread extends Thread {
@@ -502,7 +490,7 @@ public class cgBase {
loginResponse = request(true, host, path, "GET", new HashMap<String, String>(), false, false, false);
loginData = loginResponse.getData();
- if (loginData != null && loginData.length() > 0) {
+ if (StringUtils.isNotBlank(loginData)) {
if (checkLogin(loginData)) {
Log.i(cgSettings.tag, "Already logged in Geocaching.com as " + loginStart.get("username"));
@@ -513,7 +501,7 @@ public class cgBase {
viewstates = getViewstates(loginData);
- if (isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
Log.e(cgSettings.tag, "cgeoBase.login: Failed to find viewstates");
return -1; // no viewstates
}
@@ -525,7 +513,7 @@ public class cgBase {
final HashMap<String, String> login = settings.getLogin();
final HashMap<String, String> params = new HashMap<String, String>();
- if (login == null || login.get("username") == null || login.get("username").length() == 0 || login.get("password") == null || login.get("password").length() == 0) {
+ if (login == null || StringUtils.isEmpty(login.get("username")) || StringUtils.isEmpty(login.get("password"))) {
Log.e(cgSettings.tag, "cgeoBase.login: No login information stored");
return -3;
}
@@ -543,7 +531,7 @@ public class cgBase {
loginResponse = request(true, host, path, "POST", params, false, false, false);
loginData = loginResponse.getData();
- if (loginData != null && loginData.length() > 0) {
+ if (StringUtils.isNotBlank(loginData)) {
if (checkLogin(loginData)) {
Log.i(cgSettings.tag, "Successfully logged in Geocaching.com as " + login.get("username"));
@@ -578,7 +566,7 @@ public class cgBase {
}
public static Boolean checkLogin(String page) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.checkLogin: No page given");
return false;
}
@@ -611,7 +599,7 @@ public class cgBase {
}
public cgCacheWrap parseSearch(cgSearchThread thread, String url, String page, boolean showCaptcha) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.parseSearch: No page given");
return null;
}
@@ -906,7 +894,7 @@ public class cgBase {
recaptchaText = thread.getText();
}
- if (cids.size() > 0 && (recaptchaChallenge == null || (recaptchaChallenge != null && recaptchaText != null && recaptchaText.length() > 0))) {
+ if (cids.size() > 0 && (recaptchaChallenge == null || (recaptchaChallenge != null && StringUtils.isNotBlank(recaptchaText)))) {
Log.i(cgSettings.tag, "Trying to get .loc for " + cids.size() + " caches");
try {
@@ -934,7 +922,7 @@ public class cgBase {
params.append(urlencode_rfc3986(cid));
}
- if (recaptchaChallenge != null && recaptchaText != null && recaptchaText.length() > 0) {
+ if (recaptchaChallenge != null && StringUtils.isNotBlank(recaptchaText)) {
params.append("&");
params.append("recaptcha_challenge_field=");
params.append(urlencode_rfc3986(recaptchaChallenge));
@@ -947,7 +935,7 @@ public class cgBase {
final String coordinates = request(false, host, path, "POST", params.toString(), 0, true).getData();
- if (coordinates != null && coordinates.length() > 0) {
+ if ( StringUtils.isNotBlank(coordinates)) {
if (coordinates.indexOf("You have not agreed to the license agreement. The license agreement is required before you can start downloading GPX or LOC files from Geocaching.com") > -1) {
Log.i(cgSettings.tag, "User has not agreed to the license agreement. Can\'t download .loc file.");
@@ -980,7 +968,7 @@ public class cgBase {
try {
final HashMap<String, cgRating> ratings = getRating(guids, null);
- if (ratings != null) {
+ if (CollectionUtils.isNotEmpty(ratings)) {
// save found cache coordinates
for (cgCache oneCache : caches.cacheList) {
if (ratings.containsKey(oneCache.guid)) {
@@ -1001,7 +989,7 @@ public class cgBase {
}
public static cgCacheWrap parseMapJSON(String url, String data) {
- if (data == null || data.length() == 0) {
+ if (StringUtils.isEmpty(data)) {
Log.e(cgSettings.tag, "cgeoBase.parseMapJSON: No page given");
return null;
}
@@ -1020,7 +1008,7 @@ public class cgBase {
final JSONObject dataJSON = new JSONObject(json);
final JSONObject extra = dataJSON.getJSONObject("cs");
- if (extra != null && extra.length() > 0) {
+ if ( StringUtils.isNotBlank(data)) {
int count = extra.getInt("count");
if (count > 0 && extra.has("cc")) {
@@ -1085,7 +1073,7 @@ public class cgBase {
}
public cgCacheWrap parseCache(String page, int reason) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.parseCache: No page given");
return null;
}
@@ -1217,7 +1205,7 @@ public class cgBase {
tableInside = tableInside.substring(0, pos);
- if (tableInside != null && tableInside.length() > 0) {
+ if (StringUtils.isNotBlank(tableInside)) {
// cache terrain
try {
final Matcher matcherTerrain = patternTerrain.matcher(tableInside);
@@ -1584,11 +1572,9 @@ public class cgBase {
String typeStr = matcherLog.group(1);
String countStr = matcherLog.group(2);
- if (typeStr != null
- && typeStr.length() > 0
+ if (StringUtils.isNotBlank(typeStr)
&& logTypes.containsKey(typeStr.toLowerCase())
- && countStr != null
- && countStr.length() > 0)
+ && StringUtils.isNotBlank(countStr))
{
cache.logCounts.put(logTypes.get(typeStr.toLowerCase()), Integer.parseInt(countStr));
}
@@ -1718,7 +1704,7 @@ public class cgBase {
while (matcherWpType.find()) {
if (matcherWpType.groupCount() > 0) {
waypoint.type = matcherWpType.group(1);
- if (waypoint.type != null) {
+ if (StringUtils.isNotBlank(waypoint.type)) {
waypoint.type = waypoint.type.trim();
}
}
@@ -1734,7 +1720,7 @@ public class cgBase {
while (matcherWpPrefix.find()) {
if (matcherWpPrefix.groupCount() > 1) {
waypoint.prefix = matcherWpPrefix.group(2);
- if (waypoint.prefix != null) {
+ if (StringUtils.isNotBlank(waypoint.prefix)) {
waypoint.prefix = waypoint.prefix.trim();
}
}
@@ -1750,7 +1736,7 @@ public class cgBase {
while (matcherWpLookup.find()) {
if (matcherWpLookup.groupCount() > 1) {
waypoint.lookup = matcherWpLookup.group(2);
- if (waypoint.lookup != null) {
+ if (StringUtils.isNotBlank(waypoint.lookup)) {
waypoint.lookup = waypoint.lookup.trim();
}
}
@@ -1766,7 +1752,7 @@ public class cgBase {
while (matcherWpName.find()) {
if (matcherWpName.groupCount() > 0) {
waypoint.name = matcherWpName.group(1);
- if (waypoint.name != null) {
+ if (StringUtils.isNotBlank(waypoint.name)) {
waypoint.name = waypoint.name.trim();
}
}
@@ -1808,7 +1794,7 @@ public class cgBase {
while (matcherWpNote.find()) {
if (matcherWpNote.groupCount() > 0) {
waypoint.note = matcherWpNote.group(1);
- if (waypoint.note != null) {
+ if (StringUtils.isNotBlank(waypoint.note)) {
waypoint.note = waypoint.note.trim();
}
}
@@ -1845,13 +1831,13 @@ public class cgBase {
}
private static void checkFields(cgCache cache) {
- if (cache.geocode == null || cache.geocode.length() == 0) {
+ if (StringUtils.isEmpty(cache.geocode)) {
Log.w(cgSettings.tag, "geo code not parsed correctly");
}
- if (cache.name == null || cache.name.length() == 0) {
+ if (StringUtils.isEmpty(cache.name)) {
Log.w(cgSettings.tag, "name not parsed correctly");
}
- if (cache.guid == null || cache.guid.length() == 0) {
+ if (StringUtils.isEmpty(cache.guid)) {
Log.w(cgSettings.tag, "guid not parsed correctly");
}
if (cache.terrain == null || cache.terrain == 0.0) {
@@ -1860,10 +1846,10 @@ public class cgBase {
if (cache.difficulty == null || cache.difficulty == 0.0) {
Log.w(cgSettings.tag, "difficulty not parsed correctly");
}
- if (cache.owner == null || cache.owner.length() == 0) {
+ if (StringUtils.isEmpty(cache.owner)) {
Log.w(cgSettings.tag, "owner not parsed correctly");
}
- if (cache.ownerReal == null || cache.ownerReal.length() == 0) {
+ if (StringUtils.isEmpty(cache.ownerReal)) {
Log.w(cgSettings.tag, "owner real not parsed correctly");
}
if (cache.hidden == null) {
@@ -1872,10 +1858,10 @@ public class cgBase {
if (cache.favouriteCnt == null) {
Log.w(cgSettings.tag, "favoriteCount not parsed correctly");
}
- if (cache.size == null || cache.size.length() == 0) {
+ if (StringUtils.isEmpty(cache.size)) {
Log.w(cgSettings.tag, "size not parsed correctly");
}
- if (cache.type == null || cache.type.length() == 0) {
+ if (StringUtils.isNotBlank(cache.type)) {
Log.w(cgSettings.tag, "type not parsed correctly");
}
if (cache.latitude == null) {
@@ -1884,7 +1870,7 @@ public class cgBase {
if (cache.longitude == null) {
Log.w(cgSettings.tag, "longitude not parsed correctly");
}
- if (cache.location == null || cache.location.length() == 0) {
+ if (StringUtils.isEmpty(cache.location)) {
Log.w(cgSettings.tag, "location not parsed correctly");
}
}
@@ -1902,7 +1888,7 @@ public class cgBase {
private static Date parseGcCustomDate(String input)
throws ParseException
{
- if (input == null)
+ if (StringUtils.isBlank(input))
{
throw new ParseException("Input is null", 0);
}
@@ -1925,10 +1911,10 @@ public class cgBase {
ArrayList<String> guids = null;
ArrayList<String> geocodes = null;
- if (guid != null && guid.length() > 0) {
+ if (StringUtils.isNotBlank(guid)) {
guids = new ArrayList<String>();
guids.add(guid);
- } else if (geocode != null && geocode.length() > 0) {
+ } else if (StringUtils.isNotBlank(geocode)) {
geocodes = new ArrayList<String>();
geocodes.add(geocode);
} else {
@@ -1961,7 +1947,7 @@ public class cgBase {
params.put("password", login.get("password"));
}
}
- if (guids != null && guids.size() > 0) {
+ if (CollectionUtils.isNotEmpty(guids)) {
params.put("cacheIds", implode(",", guids.toArray()));
} else {
params.put("waypoints", implode(",", geocodes.toArray()));
@@ -2053,7 +2039,7 @@ public class cgBase {
}
}
- if (guid != null) {
+ if (StringUtils.isNotBlank(guid)) {
ratings.put(guid, rating);
}
}
@@ -2065,7 +2051,7 @@ public class cgBase {
}
public cgTrackable parseTrackable(String page) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.parseTrackable: No page given");
return null;
}
@@ -2142,7 +2128,7 @@ public class cgBase {
}
// trackable type
- if (trackable.name != null && trackable.name.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.name)) {
try {
final Matcher matcherType = patternType.matcher(page);
while (matcherType.find()) {
@@ -2278,10 +2264,10 @@ public class cgBase {
final String image = matcherDetailsImage.group(3);
final String details = matcherDetailsImage.group(4);
- if (image != null) {
+ if (StringUtils.isNotBlank(image)) {
trackable.image = image;
}
- if (details != null) {
+ if (StringUtils.isNotBlank(details)) {
trackable.details = details;
}
}
@@ -2388,7 +2374,7 @@ public class cgBase {
}
public static ArrayList<Integer> parseTypes(String page) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isEmpty(page)) {
return null;
}
@@ -2422,7 +2408,7 @@ public class cgBase {
}
public static ArrayList<cgTrackableLog> parseTrackableLog(String page) {
- if (page == null || page.length() == 0) {
+ if (StringUtils.isEmpty(page)) {
return null;
}
@@ -2504,7 +2490,7 @@ public class cgBase {
}
public static String stripParagraphs(String text) {
- if (text == null) {
+ if (StringUtils.isBlank(text)) {
return "";
}
@@ -2520,7 +2506,7 @@ public class cgBase {
}
public static String stripTags(String text) {
- if (text == null) {
+ if (StringUtils.isBlank(text)) {
return "";
}
@@ -2897,12 +2883,12 @@ public class cgBase {
cgCacheWrap caches = new cgCacheWrap();
String url = app.getUrl(searchId);
- if (url == null || url.length() == 0) {
+ if (StringUtils.isBlank(url)) {
Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No url found");
return searchId;
}
- if (isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No viewstate given");
return searchId;
}
@@ -2951,7 +2937,7 @@ public class cgBase {
}
}
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No data from server");
return searchId;
}
@@ -2982,13 +2968,13 @@ public class cgBase {
String geocode = parameters.get("geocode");
String guid = parameters.get("guid");
- if ((geocode == null || geocode.length() == 0) && ((guid == null || guid.length() == 0))) {
+ if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) {
Log.e(cgSettings.tag, "cgeoBase.searchByGeocode: No geocode nor guid given");
return null;
}
if (forceReload == false && reason == 0 && (app.isOffline(geocode, guid) || app.isThere(geocode, guid, true, true))) {
- if ((geocode == null || geocode.length() == 0) && guid != null && guid.length() > 0) {
+ if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) {
geocode = app.getGeocode(guid);
}
@@ -3008,9 +2994,9 @@ public class cgBase {
final String path = "/seek/cache_details.aspx";
final String method = "GET";
final HashMap<String, String> params = new HashMap<String, String>();
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
params.put("wp", geocode);
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
params.put("guid", guid);
}
params.put("decrypt", "y");
@@ -3019,9 +3005,9 @@ public class cgBase {
String page = requestLogged(false, host, path, method, params, false, false, false);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isEmpty(page)) {
if (app.isThere(geocode, guid, true, false)) {
- if ((geocode == null || geocode.length() == 0) && guid != null && guid.length() > 0) {
+ if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) {
Log.i(cgSettings.tag, "Loading old cache from cache.");
geocode = app.getGeocode(guid);
@@ -3049,7 +3035,7 @@ public class cgBase {
if (caches != null && caches.error != null && caches.error.length() > 0) {
search.error = caches.error;
}
- if (caches != null && caches.url != null && caches.url.length() > 0) {
+ if (caches != null && StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
@@ -3066,10 +3052,10 @@ public class cgBase {
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
@@ -3144,17 +3130,17 @@ public class cgBase {
cgCacheWrap caches = new cgCacheWrap();
String cacheType = parameters.get("cachetype");
- if (latitude == null || latitude.length() == 0) {
+ if (StringUtils.isBlank(latitude)) {
Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No latitude given");
return null;
}
- if (longitude == null || longitude.length() == 0) {
+ if (StringUtils.isBlank(longitude)) {
Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No longitude given");
return null;
}
- if (cacheType != null && cacheType.length() == 0) {
+ if (StringUtils.isBlank(latitude)) {
cacheType = null;
}
@@ -3173,7 +3159,7 @@ public class cgBase {
final String url = "http://" + host + path + "?" + prepareParameters(params, false, true);
String page = requestLogged(false, host, path, method, params, false, false, true);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No data from server");
return null;
}
@@ -3190,10 +3176,10 @@ public class cgBase {
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
@@ -3218,12 +3204,12 @@ public class cgBase {
cgCacheWrap caches = new cgCacheWrap();
String cacheType = parameters.get("cachetype");
- if (keyword == null || keyword.length() == 0) {
+ if (StringUtils.isBlank(keyword)) {
Log.e(cgSettings.tag, "cgeoBase.searchByKeyword: No keyword given");
return null;
}
- if (cacheType != null && cacheType.length() == 0) {
+ if (StringUtils.isBlank(cacheType)) {
cacheType = null;
}
@@ -3241,7 +3227,7 @@ public class cgBase {
final String url = "http://" + host + path + "?" + prepareParameters(params, false, true);
String page = requestLogged(false, host, path, method, params, false, false, true);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByKeyword: No data from server");
return null;
}
@@ -3258,10 +3244,10 @@ public class cgBase {
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
@@ -3286,12 +3272,12 @@ public class cgBase {
cgCacheWrap caches = new cgCacheWrap();
String cacheType = parameters.get("cachetype");
- if (userName == null || userName.length() == 0) {
+ if (StringUtils.isBlank(userName)) {
Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No user name given");
return null;
}
- if (cacheType != null && cacheType.length() == 0) {
+ if (StringUtils.isBlank(cacheType)) {
cacheType = null;
}
@@ -3315,7 +3301,7 @@ public class cgBase {
final String url = "http://" + host + path + "?" + prepareParameters(params, my, true);
String page = requestLogged(false, host, path, method, params, false, my, true);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No data from server");
return null;
}
@@ -3326,16 +3312,16 @@ public class cgBase {
}
if (app == null) {
- Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No application found");
+ Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No application found");
return null;
}
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
@@ -3360,12 +3346,12 @@ public class cgBase {
cgCacheWrap caches = new cgCacheWrap();
String cacheType = parameters.get("cachetype");
- if (userName == null || userName.length() == 0) {
+ if (StringUtils.isBlank(userName)) {
Log.e(cgSettings.tag, "cgeoBase.searchByOwner: No user name given");
return null;
}
- if (cacheType != null && cacheType.length() == 0) {
+ if (StringUtils.isBlank(cacheType)) {
cacheType = null;
}
@@ -3383,7 +3369,7 @@ public class cgBase {
final String url = "http://" + host + path + "?" + prepareParameters(params, false, true);
String page = requestLogged(false, host, path, method, params, false, false, true);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByOwner: No data from server");
return null;
}
@@ -3400,10 +3386,10 @@ public class cgBase {
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
@@ -3439,7 +3425,7 @@ public class cgBase {
String page = null;
- if (latMin == null || latMin.length() == 0 || latMax == null || latMax.length() == 0 || lonMin == null || lonMin.length() == 0 || lonMax == null || lonMax.length() == 0) {
+ if (StringUtils.isBlank(latMin) || StringUtils.isBlank(latMax) || StringUtils.isBlank(lonMin) || StringUtils.isBlank(lonMax)) {
Log.e(cgSettings.tag, "cgeoBase.searchByViewport: Not enough parameters to recognize viewport");
return null;
}
@@ -3452,7 +3438,7 @@ public class cgBase {
final String url = "http://" + host + path + "?" + params;
page = requestJSONgc(host, path, params);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchByViewport: No data from server");
return null;
}
@@ -3469,16 +3455,16 @@ public class cgBase {
final ArrayList<cgCache> cacheList = new ArrayList<cgCache>();
if (caches != null) {
- if (caches.error != null && caches.error.length() > 0) {
+ if (StringUtils.isNotBlank(caches.error)) {
search.error = caches.error;
}
- if (caches.url != null && caches.url.length() > 0) {
+ if (StringUtils.isNotBlank(caches.url)) {
search.url = caches.url;
}
search.viewstates = caches.viewstates;
search.totalCnt = caches.totalCnt;
- if (caches.cacheList != null && caches.cacheList.size() > 0) {
+ if (CollectionUtils.isNotEmpty(caches.cacheList)) {
for (cgCache cache : caches.cacheList) {
if ((settings.excludeDisabled == 0 || (settings.excludeDisabled == 1 && cache.disabled == false))
&& (settings.excludeMine == 0 || (settings.excludeMine == 1 && cache.own == false))
@@ -3519,7 +3505,7 @@ public class cgBase {
final String data = request(false, host, path, method, params, false, false, false).getData();
- if (data == null || data.length() == 0) {
+ if (StringUtils.isBlank(data)) {
Log.e(cgSettings.tag, "cgeoBase.getGeocachersInViewport: No data from server");
return null;
}
@@ -3566,7 +3552,7 @@ public class cgBase {
final String id = parameters.get("id");
cgTrackable trackable = new cgTrackable();
- if ((geocode == null || geocode.length() == 0) && (guid == null || guid.length() == 0) && (id == null || id.length() == 0)) {
+ if (StringUtils.isBlank(geocode) || StringUtils.isBlank(guid) || StringUtils.isBlank(id)) {
Log.e(cgSettings.tag, "cgeoBase.searchTrackable: No geocode nor guid nor id given");
return null;
}
@@ -3575,17 +3561,17 @@ public class cgBase {
final String path = "/track/details.aspx";
final String method = "GET";
final HashMap<String, String> params = new HashMap<String, String>();
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
params.put("tracker", geocode);
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
params.put("guid", guid);
- } else if (id != null && id.length() > 0) {
+ } else if (StringUtils.isNotBlank(id)) {
params.put("id", id);
}
String page = requestLogged(false, host, path, method, params, false, false, false);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.searchTrackable: No data from server");
return trackable;
}
@@ -3601,7 +3587,7 @@ public class cgBase {
public int postLog(cgeoapplication app, String geocode, String cacheid, String[] viewstates,
int logType, int year, int month, int day, String log, ArrayList<cgTrackableLog> trackables) {
- if (isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
Log.e(cgSettings.tag, "cgeoBase.postLog: No viewstate given");
return 1000;
}
@@ -3611,7 +3597,7 @@ public class cgBase {
return 1000;
}
- if (log == null || log.length() == 0) {
+ if (StringUtils.isBlank(log)) {
Log.e(cgSettings.tag, "cgeoBase.postLog: No log text given");
return 1001;
}
@@ -3685,7 +3671,7 @@ public class cgBase {
}
}
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.postLog: No data from server");
return 1002;
}
@@ -3698,7 +3684,7 @@ public class cgBase {
if (matcher.find() && matcher.groupCount() > 0) {
final String[] viewstatesConfirm = getViewstates(page);
- if (isEmpty(viewstatesConfirm)) {
+ if (ArrayUtils.isEmpty(viewstatesConfirm)) {
Log.e(cgSettings.tag, "cgeoBase.postLog: No viewstate for confirm log");
return 1000;
}
@@ -3763,7 +3749,7 @@ public class cgBase {
public int postLogTrackable(String tbid, String trackingCode, String[] viewstates,
int logType, int year, int month, int day, String log) {
- if (isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No viewstate given");
return 1000;
}
@@ -3773,7 +3759,7 @@ public class cgBase {
return 1000;
}
- if (log == null || log.length() == 0) {
+ if (StringUtils.isBlank(log)) {
Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No log text given");
return 1001;
}
@@ -3817,7 +3803,7 @@ public class cgBase {
}
}
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No data from server");
return 1002;
}
@@ -3847,7 +3833,7 @@ public class cgBase {
String page = requestLogged(false, "www.geocaching.com", "/my/watchlist.aspx?w=" + cache.cacheid,
"POST", null, false, false, false);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgBase.addToWatchlist: No data from server");
return -1; // error
}
@@ -3875,7 +3861,7 @@ public class cgBase {
String page = requestLogged(false, host, path, method, null, false, false, false);
- if (page == null || page.length() == 0) {
+ if (StringUtils.isBlank(page)) {
Log.e(cgSettings.tag, "cgBase.removeFromWatchlist: No data from server");
return -1; // error
}
@@ -4401,7 +4387,7 @@ public class cgBase {
if (cookiesDone == null) {
Map<String, ?> prefsValues = prefs.getAll();
- if (prefsValues != null && prefsValues.size() > 0) {
+ if (CollectionUtils.isNotEmpty(prefsValues)) {
final Set<? extends Map.Entry<String, ?>> entrySet = prefsValues.entrySet();
final ArrayList<String> cookiesEncoded = new ArrayList<String>();
@@ -4807,7 +4793,7 @@ public class cgBase {
params.put("geocode", cache.geocode);
final Long searchId = searchByGeocode(params, listId, false);
cache = app.getCache(searchId);
- } else if (geocode != null) {
+ } else if (StringUtils.isNotBlank(geocode)) {
final HashMap<String, String> params = new HashMap<String, String>();
params.put("geocode", geocode);
final Long searchId = searchByGeocode(params, listId, false);
@@ -4825,7 +4811,7 @@ public class cgBase {
final cgHtmlImg imgGetter = new cgHtmlImg(activity, cache.geocode, false, listId, true);
// store images from description
- if (cache.description != null) {
+ if (StringUtils.isNotBlank(cache.description)) {
Html.fromHtml(cache.description, imgGetter, null);
}
@@ -5121,7 +5107,7 @@ public class cgBase {
String iconTxt = null;
if (cache) {
- if (type != null && type.length() > 0) {
+ if (StringUtils.isNotBlank(type)) {
if (own) {
iconTxt = type + "-own";
} else if (found) {
@@ -5141,7 +5127,7 @@ public class cgBase {
icon = gcIcons.get("traditional");
}
} else {
- if (type != null && type.length() > 0) {
+ if (StringUtils.isNotBlank(type)) {
iconTxt = type;
} else {
iconTxt = "waypoint";
@@ -5207,7 +5193,7 @@ public class cgBase {
final String data = response.getData();
String usertoken = null;
- if (data != null && data.length() > 0) {
+ if (StringUtils.isNotBlank(data)) {
final Pattern pattern = Pattern.compile("var userToken[^=]*=[^']*'([^']+)';", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
final Matcher matcher = pattern.matcher(data);
@@ -5218,7 +5204,7 @@ public class cgBase {
}
}
- if (noTokenHandler != null && (usertoken == null || usertoken.length() == 0)) {
+ if (noTokenHandler != null && StringUtils.isBlank(usertoken)) {
noTokenHandler.sendEmptyMessage(0);
}
@@ -5235,7 +5221,7 @@ public class cgBase {
final String data = requestJSON(host, path, params);
- if (data == null || data.length() == 0) {
+ if (StringUtils.isBlank(data)) {
return elv;
}
diff --git a/src/cgeo/geocaching/cgCache.java b/src/cgeo/geocaching/cgCache.java
index 8de5a5d..0211039 100644
--- a/src/cgeo/geocaching/cgCache.java
+++ b/src/cgeo/geocaching/cgCache.java
@@ -7,6 +7,8 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -116,37 +118,37 @@ public class cgCache {
if (reason == null || reason == 0) {
reason = oldCache.reason;
}
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
geocode = oldCache.geocode;
}
- if (cacheid == null || cacheid.length() == 0) {
+ if (StringUtils.isBlank(cacheid)) {
cacheid = oldCache.cacheid;
}
- if (guid == null || guid.length() == 0) {
+ if (StringUtils.isBlank(guid)) {
guid = oldCache.guid;
}
- if (type == null || type.length() == 0) {
+ if (StringUtils.isBlank(type)) {
type = oldCache.type;
}
- if (name == null || name.length() == 0) {
+ if (StringUtils.isBlank(name)) {
name = oldCache.name;
}
- if (nameSp == null || nameSp.length() == 0) {
+ if (StringUtils.isBlank(nameSp)) {
nameSp = oldCache.nameSp;
}
- if (owner == null || owner.length() == 0) {
+ if (StringUtils.isBlank(owner)) {
owner = oldCache.owner;
}
- if (ownerReal == null || ownerReal.length() == 0) {
+ if (StringUtils.isBlank(ownerReal)) {
ownerReal = oldCache.ownerReal;
}
if (hidden == null) {
hidden = oldCache.hidden;
}
- if (hint == null || hint.length() == 0) {
+ if (StringUtils.isBlank(hint)) {
hint = oldCache.hint;
}
- if (size == null || size.length() == 0) {
+ if (StringUtils.isBlank(size)) {
size = oldCache.size;
}
if (difficulty == null || difficulty == 0) {
@@ -161,16 +163,16 @@ public class cgCache {
if (distance == null) {
distance = oldCache.distance;
}
- if (latlon == null || latlon.length() == 0) {
+ if (StringUtils.isBlank(latlon)) {
latlon = oldCache.latlon;
}
- if (latitudeString == null || latitudeString.length() == 0) {
+ if (StringUtils.isBlank(latitudeString)) {
latitudeString = oldCache.latitudeString;
}
- if (longitudeString == null || longitudeString.length() == 0) {
+ if (StringUtils.isBlank(longitudeString)) {
longitudeString = oldCache.longitudeString;
}
- if (location == null || location.length() == 0) {
+ if (StringUtils.isBlank(location)) {
location = oldCache.location;
}
if (latitude == null) {
@@ -182,13 +184,13 @@ public class cgCache {
if (elevation == null) {
elevation = oldCache.elevation;
}
- if (personalNote == null || personalNote.length() == 0) {
+ if (StringUtils.isNotBlank(personalNote)) {
personalNote = oldCache.personalNote;
}
- if (shortdesc == null || shortdesc.length() == 0) {
+ if (StringUtils.isBlank(shortdesc)) {
shortdesc = oldCache.shortdesc;
}
- if (description == null || description.length() == 0) {
+ if (StringUtils.isBlank(description)) {
description = oldCache.description;
}
if (favouriteCnt == null) {
@@ -260,7 +262,7 @@ public class cgCache {
*/
boolean isGuidContainedInPage(final String page) {
// check if the guid of the cache is anywhere in the page
- if (guid == null || guid.length() == 0) {
+ if (StringUtils.isBlank(guid)) {
return false;
}
Pattern patternOk = Pattern.compile(guid, Pattern.CASE_INSENSITIVE);
@@ -279,7 +281,7 @@ public class cgCache {
}
public boolean logVisit(IAbstractActivity fromActivity) {
- if (cacheid == null || cacheid.length() == 0) {
+ if (StringUtils.isBlank(cacheid)) {
fromActivity.showToast(((Activity)fromActivity).getResources().getString(R.string.err_cannot_log_visit));
return true;
}
diff --git a/src/cgeo/geocaching/cgCacheListAdapter.java b/src/cgeo/geocaching/cgCacheListAdapter.java
index 64bf3ea..c2d9e52 100644
--- a/src/cgeo/geocaching/cgCacheListAdapter.java
+++ b/src/cgeo/geocaching/cgCacheListAdapter.java
@@ -6,6 +6,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -35,6 +37,7 @@ import cgeo.geocaching.filter.cgFilter;
import cgeo.geocaching.sorting.CacheComparator;
import cgeo.geocaching.sorting.DistanceComparator;
import cgeo.geocaching.sorting.VisitComparator;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgCacheListAdapter extends ArrayAdapter<cgCache> {
@@ -297,13 +300,13 @@ public class cgCacheListAdapter extends ArrayAdapter<cgCache> {
lastSort = System.currentTimeMillis();
}
- if (distances != null && distances.size() > 0) {
+ if (CollectionUtils.isNotEmpty(distances)) {
for (cgDistanceView distance : distances) {
distance.update(latitudeIn, longitudeIn);
}
}
- if (compasses != null && compasses.size() > 0) {
+ if (CollectionUtils.isNotEmpty(compasses)) {
for (cgCompassMini compass : compasses) {
compass.updateCoords(latitudeIn, longitudeIn);
}
@@ -317,7 +320,7 @@ public class cgCacheListAdapter extends ArrayAdapter<cgCache> {
azimuth = azimuthIn;
- if (compasses != null && compasses.size() > 0) {
+ if (CollectionUtils.isNotEmpty(compasses)) {
for (cgCompassMini compass : compasses) {
compass.updateAzimuth(azimuth);
}
@@ -595,7 +598,7 @@ public class cgCacheListAdapter extends ArrayAdapter<cgCache> {
if (cache.geocode != null && cache.geocode.length() > 0) {
cacheInfo.append(cache.geocode);
}
- if (cache.size != null && cache.size.length() > 0) {
+ if (StringUtils.isNotBlank(cache.size)) {
if (cacheInfo.length() > 0) {
cacheInfo.append(" | ");
}
diff --git a/src/cgeo/geocaching/cgData.java b/src/cgeo/geocaching/cgData.java
index ab24864..0c15718 100644
--- a/src/cgeo/geocaching/cgData.java
+++ b/src/cgeo/geocaching/cgData.java
@@ -17,6 +17,8 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
@@ -26,6 +28,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.os.Environment;
import android.util.Log;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgData {
@@ -860,7 +863,7 @@ public class cgData {
int dataDetailed = 0;
try {
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"_id", "detailed", "detailedupdate", "updated"},
@@ -870,7 +873,7 @@ public class cgData {
null,
null,
"1");
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"_id", "detailed", "detailedupdate", "updated"},
@@ -938,7 +941,7 @@ public class cgData {
long reason = 0;
try {
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"reason"},
@@ -948,7 +951,7 @@ public class cgData {
null,
null,
"1");
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"reason"},
@@ -993,7 +996,7 @@ public class cgData {
int rel = 0;
try {
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"reliable_latlon"},
@@ -1003,7 +1006,7 @@ public class cgData {
null,
null,
"1");
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
cursor = databaseRO.query(
dbTableCaches,
new String[]{"reliable_latlon"},
@@ -1042,7 +1045,7 @@ public class cgData {
}
public String getGeocodeForGuid(String guid) {
- if (guid == null || guid.length() == 0) {
+ if (StringUtils.isBlank(guid)) {
return null;
}
@@ -1084,7 +1087,7 @@ public class cgData {
}
public String getCacheidForGeocode(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -1273,7 +1276,7 @@ public class cgData {
public boolean saveAttributes(String geocode, ArrayList<String> attributes) {
init();
- if (geocode == null || geocode.length() == 0 || attributes == null) {
+ if (StringUtils.isBlank(geocode) || attributes == null) {
return false;
}
@@ -1340,7 +1343,7 @@ public class cgData {
public boolean saveWaypoints(String geocode, ArrayList<cgWaypoint> waypoints, boolean drop) {
init();
- if (geocode == null || geocode.length() == 0 || waypoints == null) {
+ if (StringUtils.isBlank(geocode) || waypoints == null) {
return false;
}
@@ -1388,7 +1391,7 @@ public class cgData {
public boolean saveOwnWaypoint(int id, String geocode, cgWaypoint waypoint) {
init();
- if (((geocode == null || geocode.length() == 0) && id <= 0) || waypoint == null) {
+ if ((StringUtils.isBlank(geocode) && id <= 0) || waypoint == null) {
return false;
}
@@ -1447,7 +1450,7 @@ public class cgData {
public boolean saveSpoilers(String geocode, ArrayList<cgImage> spoilers) {
init();
- if (geocode == null || geocode.length() == 0 || spoilers == null) {
+ if (StringUtils.isBlank(geocode) || spoilers == null) {
return false;
}
@@ -1483,7 +1486,7 @@ public class cgData {
public boolean saveLogs(String geocode, ArrayList<cgLog> logs, boolean drop) {
init();
- if (geocode == null || geocode.length() == 0 || logs == null) {
+ if (StringUtils.isBlank(geocode) || logs == null) {
return false;
}
@@ -1508,7 +1511,7 @@ public class cgData {
long log_id = databaseRW.insert(dbTableLogs, null, values);
- if ((oneLog.logImages != null) && (oneLog.logImages.size() > 0)) {
+ if (CollectionUtils.isNotEmpty(oneLog.logImages)) {
for (cgImage img : oneLog.logImages) {
values.clear();
values.put("log_id", log_id);
@@ -1534,7 +1537,7 @@ public class cgData {
public boolean saveLogCount(String geocode, HashMap<Integer, Integer> logCounts, boolean drop) {
init();
- if (geocode == null || geocode.length() == 0 || logCounts == null || logCounts.isEmpty()) {
+ if (StringUtils.isBlank(geocode) || CollectionUtils.isEmpty(logCounts)) {
return false;
}
@@ -1681,13 +1684,13 @@ public class cgData {
Object[] geocodes = new Object[1];
Object[] guids = new Object[1];
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
geocodes[0] = geocode;
} else {
geocodes = null;
}
- if (guid != null && guid.length() > 0) {
+ if (StringUtils.isNotBlank(guid)) {
guids[0] = guid;
} else {
guids = null;
@@ -1970,7 +1973,7 @@ public class cgData {
}
public ArrayList<String> loadAttributes(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2036,7 +2039,7 @@ public class cgData {
}
public ArrayList<cgWaypoint> loadWaypoints(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2101,7 +2104,7 @@ public class cgData {
}
public ArrayList<cgImage> loadSpoilers(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2196,7 +2199,7 @@ public class cgData {
}
public ArrayList<cgLog> loadLogs(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2239,7 +2242,7 @@ public class cgData {
}
public HashMap<Integer, Integer> loadLogCounts(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2309,7 +2312,7 @@ public class cgData {
}
public ArrayList<cgTrackable> loadInventory(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2345,7 +2348,7 @@ public class cgData {
}
public cgTrackable loadTrackable(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2694,7 +2697,7 @@ public class cgData {
}
public void markStored(String geocode, int listId) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return;
}
@@ -2710,7 +2713,7 @@ public class cgData {
}
public boolean markDropped(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return false;
}
@@ -2732,7 +2735,7 @@ public class cgData {
}
public boolean markFound(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return false;
}
@@ -2863,7 +2866,7 @@ public class cgData {
cursor.close();
}
- if (geocodes.size() > 0) {
+ if (CollectionUtils.isNotEmpty(geocodes)) {
String geocodeList = cgBase.implode(", ", geocodes.toArray());
databaseRW.execSQL("delete from " + dbTableCaches + " where geocode in (" + geocodeList + ")");
databaseRW.execSQL("delete from " + dbTableAttributes + " where geocode in (" + geocodeList + ")");
@@ -2882,10 +2885,10 @@ public class cgData {
}
public boolean saveLogOffline(String geocode, Date date, int type, String log) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return false;
}
- if (type <= 0 && (log == null || log.length() == 0)) {
+ if (type <= 0 && StringUtils.isBlank(log)) {
return false;
}
@@ -2920,7 +2923,7 @@ public class cgData {
}
public cgLog loadLogOffline(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -2956,7 +2959,7 @@ public class cgData {
}
public void clearLogOffline(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return;
}
@@ -2966,7 +2969,7 @@ public class cgData {
}
public boolean hasLogOffline(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return false;
}
@@ -2985,7 +2988,7 @@ public class cgData {
}
public void saveVisitDate(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return;
}
@@ -3000,7 +3003,7 @@ public class cgData {
}
public void clearVisitDate(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return;
}
@@ -3107,7 +3110,7 @@ public class cgData {
public int createList(String name) {
int id = -1;
- if (name == null || name.length() == 0) {
+ if (StringUtils.isBlank(name)) {
return id;
}
@@ -3161,7 +3164,7 @@ public class cgData {
}
public void moveToList(String geocode, int listId) {
- if (geocode == null || geocode.length() == 0 || listId <= 0) {
+ if (StringUtils.isBlank(geocode) || listId <= 0) {
return;
}
@@ -3187,7 +3190,7 @@ public class cgData {
public boolean removeSearchedDestination(cgDestination destination) {
boolean success = true;
- if(destination == null){
+ if (destination == null){
success = false;
} else{
init();
diff --git a/src/cgeo/geocaching/cgDirectionImg.java b/src/cgeo/geocaching/cgDirectionImg.java
index 573cf47..33f186a 100644
--- a/src/cgeo/geocaching/cgDirectionImg.java
+++ b/src/cgeo/geocaching/cgDirectionImg.java
@@ -5,6 +5,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -20,11 +21,11 @@ public class cgDirectionImg {
String dirName;
String fileName;
- if (geocode == null || geocode.length() == 0 || code == null || code.length() == 0) {
+ if (StringUtils.isBlank(geocode) || code == null || code.length() == 0) {
return;
}
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
dirName = cgSettings.getStorage() + geocode + "/";
fileName = cgSettings.getStorage() + geocode + "/direction.png";
} else {
diff --git a/src/cgeo/geocaching/cgGeo.java b/src/cgeo/geocaching/cgGeo.java
index 4ae2e32..303fc49 100644
--- a/src/cgeo/geocaching/cgGeo.java
+++ b/src/cgeo/geocaching/cgGeo.java
@@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.location.GpsSatellite;
@@ -407,7 +409,7 @@ public class cgGeo {
}
final String res = base.request(false, host, path, method, params, false, false, false).getData();
- if (res != null && res.length() > 0) {
+ if (StringUtils.isNotBlank(res)) {
lastGo4cacheLat = latitudeNow;
lastGo4cacheLon = longitudeNow;
}
diff --git a/src/cgeo/geocaching/cgHtmlImg.java b/src/cgeo/geocaching/cgHtmlImg.java
index e6e5a2e..2fdcb66 100644
--- a/src/cgeo/geocaching/cgHtmlImg.java
+++ b/src/cgeo/geocaching/cgHtmlImg.java
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
+import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -67,7 +68,7 @@ public class cgHtmlImg implements Html.ImageGetter {
String fileName = null;
String fileNameSec = null;
- if (url == null || url.length() == 0) {
+ if (StringUtils.isNotBlank(url)) {
return null;
}
@@ -82,7 +83,7 @@ public class cgHtmlImg implements Html.ImageGetter {
urlExt = "";
}
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
dirName = cgSettings.getStorage() + geocode + "/";
fileName = cgSettings.getStorage() + geocode + "/" + cgBase.md5(url) + urlExt;
fileNameSec = cgSettings.getStorageSec() + geocode + "/" + cgBase.md5(url) + urlExt;
diff --git a/src/cgeo/geocaching/cgSettings.java b/src/cgeo/geocaching/cgSettings.java
index b2d2cf5..e5a9027 100644
--- a/src/cgeo/geocaching/cgSettings.java
+++ b/src/cgeo/geocaching/cgSettings.java
@@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
import org.mapsforge.android.maps.MapDatabase;
import android.content.Context;
@@ -16,6 +17,7 @@ import android.util.Log;
import cgeo.geocaching.googlemaps.googleMapFactory;
import cgeo.geocaching.mapinterfaces.MapFactory;
import cgeo.geocaching.mapsforge.mfMapFactory;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgSettings {
@@ -268,7 +270,7 @@ public class cgSettings {
final String preUsername = prefs.getString(KEY_USERNAME, null);
final String prePassword = prefs.getString(KEY_PASSWORD, null);
- if (preUsername == null || prePassword == null || preUsername.length() == 0 || prePassword.length() == 0) {
+ if (StringUtils.isBlank(preUsername) || StringUtils.isBlank(prePassword)) {
return false;
} else {
return true;
@@ -325,7 +327,7 @@ public class cgSettings {
@Override
public void edit(Editor edit) {
- if (username == null || username.length() == 0 || password == null || password.length() == 0) {
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
// erase username and password
edit.remove(KEY_USERNAME);
edit.remove(KEY_PASSWORD);
@@ -342,7 +344,7 @@ public class cgSettings {
final String preUsername = prefs.getString(KEY_USERNAME, null);
final String prePassword = prefs.getString(KEY_GCVOTE_PASSWORD, null);
- if (preUsername == null || prePassword == null || preUsername.length() == 0 || prePassword.length() == 0) {
+ if (StringUtils.isBlank(preUsername) || StringUtils.isBlank(prePassword)) {
return false;
} else {
return true;
@@ -354,7 +356,7 @@ public class cgSettings {
@Override
public void edit(Editor edit) {
- if (password == null || password.length() == 0) {
+ if (StringUtils.isBlank(password)) {
// erase password
edit.remove(KEY_GCVOTE_PASSWORD);
} else {
@@ -393,7 +395,7 @@ public class cgSettings {
@Override
public void edit(Editor edit) {
- if (signature == null || signature.length() == 0) {
+ if (StringUtils.isBlank(signature)) {
// erase signature
edit.remove(KEY_SIGNATURE);
} else {
@@ -427,7 +429,7 @@ public class cgSettings {
// delete cookies
Map<String, ?> prefsValues = prefs.getAll();
- if (prefsValues != null && prefsValues.size() > 0) {
+ if (CollectionUtils.isNotEmpty(prefsValues)) {
Log.i(cgSettings.tag, "Removing cookies");
for (String key : prefsValues.keySet()) {
diff --git a/src/cgeo/geocaching/cgWaypoint.java b/src/cgeo/geocaching/cgWaypoint.java
index 7746c2c..c41b97a 100644
--- a/src/cgeo/geocaching/cgWaypoint.java
+++ b/src/cgeo/geocaching/cgWaypoint.java
@@ -2,6 +2,8 @@ package cgeo.geocaching;
import java.util.ArrayList;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.widget.TextView;
@@ -38,16 +40,16 @@ public class cgWaypoint {
if (lookup == null || lookup.length() == 0) {
lookup = old.lookup;
}
- if (name == null || name.length() == 0) {
+ if (StringUtils.isBlank(name)) {
name = old.name;
}
- if (latlon == null || latlon.length() == 0) {
+ if (StringUtils.isBlank(latlon)) {
latlon = old.latlon;
}
- if (latitudeString == null || latitudeString.length() == 0) {
+ if (StringUtils.isBlank(latitudeString)) {
latitudeString = old.latitudeString;
}
- if (longitudeString == null || longitudeString.length() == 0) {
+ if (StringUtils.isBlank(longitudeString)) {
longitudeString = old.longitudeString;
}
if (latitude == null) {
diff --git a/src/cgeo/geocaching/cgeo.java b/src/cgeo/geocaching/cgeo.java
index fcc1248..6204fad 100644
--- a/src/cgeo/geocaching/cgeo.java
+++ b/src/cgeo/geocaching/cgeo.java
@@ -7,6 +7,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -29,6 +31,7 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.activity.ActivityMixin;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeo extends AbstractActivity {
@@ -84,7 +87,7 @@ public class cgeo extends AbstractActivity {
@Override
public void handleMessage(Message msg) {
try {
- if (addresses != null && addresses.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(addresses)) {
final Address address = addresses.get(0);
final StringBuilder addText = new StringBuilder();
@@ -287,7 +290,7 @@ public class cgeo extends AbstractActivity {
if (requestCode == SCAN_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
String scan = intent.getStringExtra("SCAN_RESULT");
- if (scan == null || scan.length() == 0) {
+ if (StringUtils.isBlank(scan)) {
return;
}
String host = "http://coord.info/";
diff --git a/src/cgeo/geocaching/cgeoadvsearch.java b/src/cgeo/geocaching/cgeoadvsearch.java
index f6f2159..ee118df 100644
--- a/src/cgeo/geocaching/cgeoadvsearch.java
+++ b/src/cgeo/geocaching/cgeoadvsearch.java
@@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Configuration;
@@ -245,7 +247,7 @@ public class cgeoadvsearch extends AbstractActivity {
final String latText = latView.getText().toString();
final String lonText = lonView.getText().toString();
- if (latText == null || latText.length() == 0 || lonText == null || lonText.length() == 0) {
+ if (StringUtils.isEmpty(latText) || StringUtils.isEmpty(lonText)) {
latView.setText(cgBase.formatCoordinate(geo.latitudeNow, "lat", true));
lonView.setText(cgBase.formatCoordinate(geo.longitudeNow, "lon", true));
} else {
@@ -295,7 +297,7 @@ public class cgeoadvsearch extends AbstractActivity {
// find caches by coordinates
String keyText = ((EditText) findViewById(R.id.keyword)).getText().toString();
- if (keyText == null || keyText.length() == 0) {
+ if (StringUtils.isBlank(keyText)) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_keyword));
return;
}
@@ -330,7 +332,7 @@ public class cgeoadvsearch extends AbstractActivity {
private void findByAddressFn() {
final String addText = ((EditText) findViewById(R.id.address)).getText().toString();
- if (addText == null || addText.length() == 0) {
+ if (StringUtils.isBlank(addText)) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_address));
return;
}
@@ -363,7 +365,7 @@ public class cgeoadvsearch extends AbstractActivity {
public void findByUsernameFn() {
final String usernameText = ((EditText) findViewById(R.id.username)).getText().toString();
- if (usernameText == null || usernameText.length() == 0) {
+ if (StringUtils.isBlank(usernameText)) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user));
return;
}
@@ -398,7 +400,7 @@ public class cgeoadvsearch extends AbstractActivity {
private void findByOwnerFn() {
final String usernameText = ((EditText) findViewById(R.id.owner)).getText().toString();
- if (usernameText == null || usernameText.length() == 0) {
+ if (StringUtils.isBlank(usernameText)) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user));
return;
}
@@ -433,7 +435,7 @@ public class cgeoadvsearch extends AbstractActivity {
private void findByGeocodeFn() {
final String geocodeText = ((EditText) findViewById(R.id.geocode)).getText().toString();
- if (geocodeText == null || geocodeText.length() == 0 || geocodeText.equalsIgnoreCase("GC")) {
+ if (StringUtils.isBlank(geocodeText) || geocodeText.equalsIgnoreCase("GC")) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_gccode));
return;
}
@@ -464,7 +466,7 @@ public class cgeoadvsearch extends AbstractActivity {
private void findTrackableFn() {
final String trackableText = ((EditText) findViewById(R.id.trackable)).getText().toString();
- if (trackableText == null || trackableText.length() == 0 || trackableText.equalsIgnoreCase("TB")) {
+ if (StringUtils.isBlank(trackableText)|| trackableText.equalsIgnoreCase("TB")) {
helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_tb));
return;
}
diff --git a/src/cgeo/geocaching/cgeoapplication.java b/src/cgeo/geocaching/cgeoapplication.java
index e165c0f..bb76d58 100644
--- a/src/cgeo/geocaching/cgeoapplication.java
+++ b/src/cgeo/geocaching/cgeoapplication.java
@@ -6,9 +6,13 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Application;
import android.content.Context;
import android.util.Log;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeoapplication extends Application {
@@ -265,7 +269,7 @@ public class cgeoapplication extends Application {
}
public boolean setViewstates(Long searchId, String[] viewstates) {
- if (cgBase.isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
return false;
}
if (searchId == null || searches.containsKey(searchId) == false) {
@@ -316,7 +320,7 @@ public class cgeoapplication extends Application {
}
public cgCache getCacheByGeocode(String geocode, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -338,7 +342,7 @@ public class cgeoapplication extends Application {
}
public cgTrackable getTrackableByGeocode(String geocode) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return null;
}
@@ -655,7 +659,7 @@ public class cgeoapplication extends Application {
}
public void addGeocode(Long searchId, String geocode) {
- if (this.searches.containsKey(searchId) == false || geocode == null || geocode.length() == 0) {
+ if (this.searches.containsKey(searchId) == false || StringUtils.isBlank(geocode)) {
return;
}
@@ -673,7 +677,7 @@ public class cgeoapplication extends Application {
}
public Long addSearch(final cgSearch search, final ArrayList<cgCache> cacheList, final boolean newItem, final int reason) {
- if (cacheList == null || cacheList.isEmpty()) {
+ if (CollectionUtils.isEmpty(cacheList)) {
return null;
}
@@ -766,7 +770,7 @@ public class cgeoapplication extends Application {
}
public boolean addLog(String geocode, cgLog log) {
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
return false;
}
if (log == null) {
diff --git a/src/cgeo/geocaching/cgeoauth.java b/src/cgeo/geocaching/cgeoauth.java
index 0343adf..31647df 100644
--- a/src/cgeo/geocaching/cgeoauth.java
+++ b/src/cgeo/geocaching/cgeoauth.java
@@ -14,6 +14,8 @@ import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -119,7 +121,7 @@ public class cgeoauth extends AbstractActivity {
startButton.setEnabled(true);
startButton.setOnClickListener(new startListener());
- if (OAtoken == null || OAtoken.length() == 0 || OAtokenSecret == null || OAtokenSecret.length() == 0) {
+ if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) {
// start authorization process
startButton.setText(res.getString(R.string.auth_start));
} else {
@@ -185,7 +187,7 @@ public class cgeoauth extends AbstractActivity {
final String line = sb.toString();
- if (line != null && line.length() > 0) {
+ if (StringUtils.isNotBlank(line)) {
final Matcher paramsMatcher1 = paramsPattern1.matcher(line);
if (paramsMatcher1.find() && paramsMatcher1.groupCount() > 0) {
OAtoken = paramsMatcher1.group(1);
@@ -195,7 +197,7 @@ public class cgeoauth extends AbstractActivity {
OAtokenSecret = paramsMatcher2.group(1);
}
- if (OAtoken != null && OAtoken.length() > 0 && OAtokenSecret != null && OAtokenSecret.length() > 0) {
+ if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) {
final SharedPreferences.Editor prefsEdit = getSharedPreferences(cgSettings.preferences, 0).edit();
prefsEdit.putString("temp-token-public", OAtoken);
prefsEdit.putString("temp-token-secret", OAtokenSecret);
@@ -303,7 +305,7 @@ public class cgeoauth extends AbstractActivity {
OAtokenSecret = paramsMatcher2.group(1);
}
- if (OAtoken.length() == 0 || OAtokenSecret.length() == 0) {
+ if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) {
OAtoken = "";
OAtokenSecret = "";
diff --git a/src/cgeo/geocaching/cgeocaches.java b/src/cgeo/geocaching/cgeocaches.java
index e8586cc..18fddfd 100644
--- a/src/cgeo/geocaching/cgeocaches.java
+++ b/src/cgeo/geocaching/cgeocaches.java
@@ -14,6 +14,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
@@ -58,6 +60,7 @@ import cgeo.geocaching.sorting.RatingComparator;
import cgeo.geocaching.sorting.SizeComparator;
import cgeo.geocaching.sorting.TerrainComparator;
import cgeo.geocaching.sorting.VoteComparator;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeocaches extends AbstractListActivity {
@@ -168,7 +171,7 @@ public class cgeocaches extends AbstractListActivity {
cacheList.clear();
final ArrayList<cgCache> cacheListTmp = app.getCaches(searchId);
- if (cacheListTmp != null && cacheListTmp.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheListTmp)) {
cacheList.addAll(cacheListTmp);
cacheListTmp.clear();
@@ -266,7 +269,7 @@ public class cgeocaches extends AbstractListActivity {
cacheList.clear();
final ArrayList<cgCache> cacheListTmp = app.getCaches(searchId);
- if (cacheListTmp != null && cacheListTmp.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheListTmp)) {
cacheList.addAll(cacheListTmp);
cacheListTmp.clear();
Collections.sort((List<cgCache>)cacheList, gcComparator);
@@ -355,7 +358,7 @@ public class cgeocaches extends AbstractListActivity {
} else {
if (cacheList != null && searchId != null) {
final ArrayList<cgCache> cacheListTmp = app.getCaches(searchId);
- if (cacheListTmp != null && cacheListTmp.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheListTmp)) {
cacheList.clear();
cacheList.addAll(cacheListTmp);
cacheListTmp.clear();
@@ -417,7 +420,7 @@ public class cgeocaches extends AbstractListActivity {
cacheList.clear();
final ArrayList<cgCache> cacheListTmp = app.getCaches(searchId);
- if (cacheListTmp != null && cacheListTmp.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheListTmp)) {
cacheList.addAll(cacheListTmp);
cacheListTmp.clear();
@@ -450,7 +453,7 @@ public class cgeocaches extends AbstractListActivity {
cacheList.clear();
final ArrayList<cgCache> cacheListTmp = app.getCaches(searchId);
- if (cacheListTmp != null && cacheListTmp.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheListTmp)) {
cacheList.addAll(cacheListTmp);
cacheListTmp.clear();
@@ -618,7 +621,7 @@ public class cgeocaches extends AbstractListActivity {
thread.start();
} else if (type.equals("address")) {
action = "planning";
- if (address != null && address.length() > 0) {
+ if (StringUtils.isNotBlank(address)) {
title = address;
setTitle(title);
showProgress(true);
@@ -1081,7 +1084,7 @@ public class cgeocaches extends AbstractListActivity {
}
final cgCache cache = adapter.getItem(adapterInfo.position);
- if (cache.name != null && cache.name.length() > 0) {
+ if (StringUtils.isNotBlank(cache.name)) {
menu.setHeaderTitle(cache.name);
} else {
menu.setHeaderTitle(cache.geocode);
@@ -1359,7 +1362,7 @@ public class cgeocaches extends AbstractListActivity {
}
if (more == false) {
- if (cacheList == null || cacheList.isEmpty()) {
+ if (CollectionUtils.isEmpty(cacheList)) {
listFooterText.setText(res.getString(R.string.caches_no_cache));
} else {
listFooterText.setText(res.getString(R.string.caches_more_caches_no));
@@ -1386,7 +1389,7 @@ public class cgeocaches extends AbstractListActivity {
setTitle(title);
}
- if (cacheList != null && cacheList.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(cacheList)) {
final Integer count = app.getTotal(searchId);
if (count != null && count > 0) {
setTitle(title);
@@ -1877,7 +1880,7 @@ public class cgeocaches extends AbstractListActivity {
username = usernameIn;
cachetype = cachetypeIn;
- if (username == null || username.length() == 0) {
+ if (StringUtils.isBlank(username)) {
showToast(res.getString(R.string.warn_no_username));
finish();
@@ -1910,7 +1913,7 @@ public class cgeocaches extends AbstractListActivity {
username = usernameIn;
cachetype = cachetypeIn;
- if (username == null || username.length() == 0) {
+ if (StringUtils.isBlank(username)) {
showToast(res.getString(R.string.warn_no_username));
finish();
@@ -2473,7 +2476,7 @@ public class cgeocaches extends AbstractListActivity {
value = value.trim();
}
- if (value != null && value.length() > 0) {
+ if (StringUtils.isNotBlank(value)) {
int newId = app.createList(value);
if (newId >= 10) {
@@ -2520,7 +2523,7 @@ public class cgeocaches extends AbstractListActivity {
}
public void goMap(View view) {
- if (searchId == null || searchId == 0 || cacheList == null || cacheList.isEmpty()) {
+ if (searchId == null || searchId == 0 || CollectionUtils.isEmpty(cacheList)) {
showToast(res.getString(R.string.warn_no_cache_coord));
return;
diff --git a/src/cgeo/geocaching/cgeocoords.java b/src/cgeo/geocaching/cgeocoords.java
index 229b3a6..15944ae 100644
--- a/src/cgeo/geocaching/cgeocoords.java
+++ b/src/cgeo/geocaching/cgeocoords.java
@@ -3,6 +3,8 @@ package cgeo.geocaching;
import java.util.ArrayList;
import java.util.HashMap;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Dialog;
import android.os.Bundle;
import android.text.Editable;
@@ -274,7 +276,7 @@ public class cgeocoords extends Dialog {
public void onClick(View v) {
Button e = (Button) v;
CharSequence text = e.getText();
- if (text == null || text.length() == 0) {
+ if (StringUtils.isBlank(text)) {
return;
}
switch (text.charAt(0)) {
diff --git a/src/cgeo/geocaching/cgeodetail.java b/src/cgeo/geocaching/cgeodetail.java
index dbd4614..a9cef35 100644
--- a/src/cgeo/geocaching/cgeodetail.java
+++ b/src/cgeo/geocaching/cgeodetail.java
@@ -9,6 +9,8 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map.Entry;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentValues;
@@ -54,6 +56,7 @@ import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.apps.cache.GeneralAppsFactory;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.compatibility.Compatibility;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeodetail extends AbstractActivity {
@@ -320,10 +323,10 @@ public class cgeodetail extends AbstractActivity {
geocode = uri.getQueryParameter("wp");
guid = uri.getQueryParameter("guid");
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
geocode = geocode.toUpperCase();
guid = null;
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
geocode = null;
guid = guid.toLowerCase();
} else {
@@ -352,9 +355,9 @@ public class cgeodetail extends AbstractActivity {
app.setAction(geocode);
try {
- if (name != null && name.length() > 0) {
+ if (StringUtils.isNotBlank(name)) {
waitDialog = ProgressDialog.show(this, name, res.getString(R.string.cache_dialog_loading_details), true);
- } else if (geocode != null && geocode.length() > 0) {
+ } else if (StringUtils.isNotBlank(geocode)) {
waitDialog = ProgressDialog.show(this, geocode.toUpperCase(), res.getString(R.string.cache_dialog_loading_details), true);
} else {
waitDialog = ProgressDialog.show(this, res.getString(R.string.cache), res.getString(R.string.cache_dialog_loading_details), true);
@@ -423,7 +426,7 @@ public class cgeodetail extends AbstractActivity {
if (viewId == R.id.author) { // Author of a log entry
contextMenuUser = ((TextView)view).getText().toString();
} else if (viewId == R.id.value) { // The owner of the cache
- if (cache.ownerReal != null && cache.ownerReal.length() > 0) {
+ if (StringUtils.isNotBlank(cache.ownerReal)) {
contextMenuUser = cache.ownerReal;
} else {
contextMenuUser = cache.owner;
@@ -493,7 +496,7 @@ public class cgeodetail extends AbstractActivity {
}
addVisitMenu(menu, cache);
- if (cache != null && cache.spoilers != null && cache.spoilers.size() > 0) {
+ if (cache != null && CollectionUtils.isNotEmpty(cache.spoilers)) {
menu.add(1, 5, 0, res.getString(R.string.cache_menu_spoilers)).setIcon(android.R.drawable.ic_menu_gallery); // spoiler images
}
@@ -570,7 +573,7 @@ public class cgeodetail extends AbstractActivity {
}
}
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
app.setAction(geocode);
}
}
@@ -589,7 +592,7 @@ public class cgeodetail extends AbstractActivity {
if (cache == null) {
if (waitDialog != null && waitDialog.isShowing()) waitDialog.dismiss();
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
showToast(res.getString(R.string.err_detail_cache_find) + " " + geocode + ".");
} else {
geocode = null;
@@ -618,13 +621,11 @@ public class cgeodetail extends AbstractActivity {
gcIcons.put("mystery", R.drawable.type_mystery);
}
- if (null == geocode && cache.geocode.length() > 0)
- {
+ if (StringUtils.isBlank(geocode)) {
geocode = cache.geocode;
}
- if (null == guid && cache.guid.length() > 0)
- {
+ if (StringUtils.isBlank(guid)) {
guid = cache.guid;
}
@@ -666,7 +667,7 @@ public class cgeodetail extends AbstractActivity {
itemName.setText(res.getString(R.string.cache_type));
String size = "";
- if (cache.size != null && cache.size.length() > 0) {
+ if (StringUtils.isNotBlank(cache.size)) {
// don't show "not chosen" for events, that should be the normal case
if (!(cache.isEventCache() && cache.size.equals("not chosen"))) {
size = " (" + cache.size + ")";
@@ -781,15 +782,15 @@ public class cgeodetail extends AbstractActivity {
}
// cache author
- if ((cache.owner != null && cache.owner.length() > 0) || (cache.ownerReal != null && cache.ownerReal.length() > 0)) {
+ if (StringUtils.isNotBlank(cache.owner) || StringUtils.isNotBlank(cache.ownerReal)) {
itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null);
itemName = (TextView) itemLayout.findViewById(R.id.name);
itemValue = (TextView) itemLayout.findViewById(R.id.value);
itemName.setText(res.getString(R.string.cache_owner));
- if (cache.owner != null && cache.owner.length() > 0) {
+ if (StringUtils.isNotBlank(cache.owner)) {
itemValue.setText(Html.fromHtml(cache.owner), TextView.BufferType.SPANNABLE);
- } else if (cache.ownerReal != null && cache.ownerReal.length() > 0) {
+ } else if (StringUtils.isNotBlank(cache.ownerReal)) {
itemValue.setText(Html.fromHtml(cache.ownerReal), TextView.BufferType.SPANNABLE);
}
itemValue.setOnClickListener(new userActions());
@@ -812,7 +813,7 @@ public class cgeodetail extends AbstractActivity {
}
// cache location
- if (cache.location != null && cache.location.length() > 0) {
+ if (StringUtils.isNotBlank(cache.location)) {
itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null);
itemName = (TextView) itemLayout.findViewById(R.id.name);
itemValue = (TextView) itemLayout.findViewById(R.id.value);
@@ -834,7 +835,7 @@ public class cgeodetail extends AbstractActivity {
}
// cache attributes
- if (cache.attributes != null && cache.attributes.size() > 0) {
+ if (CollectionUtils.isNotEmpty(cache.attributes)) {
final LinearLayout attribBox = (LinearLayout) findViewById(R.id.attributes_innerbox);
@@ -871,7 +872,7 @@ public class cgeodetail extends AbstractActivity {
}
// cache inventory
- if (cache.inventory != null && cache.inventory.size() > 0) {
+ if (CollectionUtils.isNotEmpty(cache.inventory)) {
final LinearLayout inventBox = (LinearLayout) findViewById(R.id.inventory_box);
final TextView inventView = (TextView) findViewById(R.id.inventory);
@@ -933,7 +934,7 @@ public class cgeodetail extends AbstractActivity {
offlineRefresh.setClickable(true);
// cache personal note
- if (cache.personalNote != null && cache.personalNote.length() > 0) {
+ if (StringUtils.isNotBlank(cache.personalNote)) {
((LinearLayout) findViewById(R.id.personalnote_box)).setVisibility(View.VISIBLE);
TextView personalNoteText = (TextView) findViewById(R.id.personalnote);
@@ -943,7 +944,7 @@ public class cgeodetail extends AbstractActivity {
}
// cache short desc
- if (cache.shortdesc != null && cache.shortdesc.length() > 0) {
+ if (StringUtils.isNotBlank(cache.shortdesc)) {
((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE);
TextView descView = (TextView) findViewById(R.id.shortdesc);
@@ -956,7 +957,7 @@ public class cgeodetail extends AbstractActivity {
if (longDescDisplayed) {
parseLongDescription();
- if (longDesc != null && longDesc.length() > 0) {
+ if (StringUtils.isNotBlank(longDesc)) {
((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE);
TextView descView = (TextView) findViewById(R.id.description);
@@ -969,7 +970,7 @@ public class cgeodetail extends AbstractActivity {
showDesc.setOnTouchListener(null);
showDesc.setOnClickListener(null);
}
- } else if (longDescDisplayed == false && cache.description != null && cache.description.length() > 0) {
+ } else if (longDescDisplayed == false && StringUtils.isNotBlank(cache.description)) {
((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE);
Button showDesc = (Button) findViewById(R.id.show_description);
@@ -992,7 +993,7 @@ public class cgeodetail extends AbstractActivity {
LinearLayout waypoints = (LinearLayout) findViewById(R.id.waypoints);
waypoints.removeAllViews();
- if (cache.waypoints != null && cache.waypoints.size() > 0) {
+ if (CollectionUtils.isNotEmpty(cache.waypoints)) {
LinearLayout waypointView;
// sort waypoints: PP, Sx, FI, OWN
@@ -1006,7 +1007,7 @@ public class cgeodetail extends AbstractActivity {
}
private int order(cgWaypoint waypoint) {
- if (waypoint.prefix == null || waypoint.prefix.length() == 0) {
+ if (StringUtils.isEmpty(waypoint.prefix)) {
return 0;
}
// check only the first character. sometimes there are inconsistencies like FI or FN for the FINAL
@@ -1040,7 +1041,7 @@ public class cgeodetail extends AbstractActivity {
}
TextView nameView = (TextView) waypointView.findViewById(R.id.name);
- if (wpt.name.trim().length() == 0) {
+ if (StringUtils.isBlank(wpt.name)) {
nameView.setText(cgBase.formatCoordinate(wpt.latitude, "lat", true) + " | " + cgBase.formatCoordinate(wpt.longitude, "lon", true));
} else {
// avoid HTML parsing
@@ -1072,7 +1073,7 @@ public class cgeodetail extends AbstractActivity {
addWaypoint.setOnClickListener(new addWaypoint());
// cache hint
- if (cache.hint != null && cache.hint.length() > 0) {
+ if (StringUtils.isNotBlank(cache.hint)) {
((LinearLayout) findViewById(R.id.hint_box)).setVisibility(View.VISIBLE);
TextView hintView = ((TextView) findViewById(R.id.hint));
hintView.setText(cgBase.rot13(cache.hint.trim()));
@@ -1309,9 +1310,9 @@ public class cgeodetail extends AbstractActivity {
@Override
public void run() {
HashMap<String, String> params = new HashMap<String, String>();
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
params.put("geocode", geocode);
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
params.put("guid", guid);
} else {
return;
@@ -1392,7 +1393,7 @@ public class cgeodetail extends AbstractActivity {
// cache
coords = new cgCoord();
coords.type = "cache";
- if (name != null && name.length() > 0) {
+ if (StringUtils.isNotBlank(name)) {
coords.name = name;
} else {
coords.name = geocode.toUpperCase();
@@ -1506,11 +1507,11 @@ public class cgeodetail extends AbstractActivity {
description.append("http://coord.info/");
description.append(cache.geocode.toUpperCase());
description.append("\n\n");
- if (cache.shortdesc != null && cache.shortdesc.length() > 0) {
+ if (StringUtils.isNotBlank(cache.shortdesc)) {
description.append(Html.fromHtml(cache.shortdesc).toString());
}
- if (cache.personalNote != null && cache.personalNote.length() > 0) {
+ if (StringUtils.isNotBlank(cache.personalNote)) {
description.append("\n\n"+Html.fromHtml(cache.personalNote).toString());
}
@@ -1522,10 +1523,10 @@ public class cgeodetail extends AbstractActivity {
event.put("title", Html.fromHtml(cache.name).toString());
event.put("description", description.toString());
String location = "";
- if (cache.latitudeString != null && cache.latitudeString.length() > 0 && cache.longitudeString != null && cache.longitudeString.length() > 0) {
+ if (StringUtils.isNotBlank(cache.latitudeString) && StringUtils.isNotBlank(cache.longitudeString)) {
location += cache.latitudeString + " " + cache.longitudeString;
}
- if (cache.location != null && cache.location.length() > 0) {
+ if (StringUtils.isNotBlank(cache.location)) {
boolean addParenteses = false;
if (location.length() > 0) {
addParenteses = true;
@@ -1583,7 +1584,7 @@ public class cgeodetail extends AbstractActivity {
if (cache != null && cache.geocode != null) {
String subject = cache.geocode.toUpperCase();
- if (cache.name != null && cache.name.length() > 0){
+ if (StringUtils.isNotBlank(cache.name)){
subject = subject + " - " + cache.name;
}
intent.putExtra(Intent.EXTRA_SUBJECT, "Geocache " + subject);
@@ -2067,7 +2068,7 @@ public class cgeodetail extends AbstractActivity {
int id = res.getIdentifier("attribute_" + attribute, "string", base.context.getPackageName());
if (id > 0) {
String translated = res.getString(id);
- if (translated != null && translated.length() > 0) {
+ if (StringUtils.isNotBlank(translated)) {
attribute = translated;
}
}
diff --git a/src/cgeo/geocaching/cgeoinit.java b/src/cgeo/geocaching/cgeoinit.java
index 46ba767..afedec3 100644
--- a/src/cgeo/geocaching/cgeoinit.java
+++ b/src/cgeo/geocaching/cgeoinit.java
@@ -2,6 +2,8 @@ package cgeo.geocaching;
import java.io.File;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -261,7 +263,7 @@ public class cgeoinit extends AbstractActivity {
});
CheckBox twitterButton = (CheckBox) findViewById(R.id.twitter_option);
- if (prefs.getInt("twitter", 0) == 0 || settings.tokenPublic == null || settings.tokenPublic.length() == 0 || settings.tokenSecret == null || settings.tokenSecret.length() == 0) {
+ if (prefs.getInt("twitter", 0) == 0 || StringUtils.isBlank(settings.tokenPublic) || StringUtils.isBlank(settings.tokenSecret)) {
twitterButton.setChecked(false);
} else {
twitterButton.setChecked(true);
@@ -435,7 +437,7 @@ public class cgeoinit extends AbstractActivity {
//Send2cgeo settings
String webDeviceName = prefs.getString("webDeviceName", null);
- if ((webDeviceName != null) &&(webDeviceName.length() > 0)) {
+ if (StringUtils.isNotBlank(webDeviceName)) {
((EditText) findViewById(R.id.webDeviceName)).setText(webDeviceName);
} else {
String s = android.os.Build.MODEL;
@@ -579,7 +581,7 @@ public class cgeoinit extends AbstractActivity {
}
edit.commit();
- if (settings.twitter == 1 && (settings.tokenPublic == null || settings.tokenPublic.length() == 0 || settings.tokenSecret == null || settings.tokenSecret.length() == 0)) {
+ if (settings.twitter == 1 && (StringUtils.isBlank(settings.tokenPublic) || StringUtils.isBlank(settings.tokenSecret))) {
Intent authIntent = new Intent(cgeoinit.this, cgeoauth.class);
startActivity(authIntent);
}
@@ -1009,7 +1011,7 @@ public class cgeoinit extends AbstractActivity {
final String username = ((EditText) findViewById(R.id.username)).getText().toString();
final String password = ((EditText) findViewById(R.id.password)).getText().toString();
- if (username == null || username.length() == 0 || password == null || password.length() == 0) {
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
showToast(res.getString(R.string.err_missing_auth));
return;
}
@@ -1037,7 +1039,7 @@ public class cgeoinit extends AbstractActivity {
final String deviceCode = prefs.getString("webDeviceCode", null);
- if (deviceName == null || deviceName.length() == 0) {
+ if (StringUtils.isBlank(deviceName)) {
showToast(res.getString(R.string.err_missing_device_name));
return;
}
diff --git a/src/cgeo/geocaching/cgeonavigate.java b/src/cgeo/geocaching/cgeonavigate.java
index 2f80d29..3cb1371 100644
--- a/src/cgeo/geocaching/cgeonavigate.java
+++ b/src/cgeo/geocaching/cgeonavigate.java
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -86,8 +88,8 @@ public class cgeonavigate extends AbstractActivity {
dstLatitude = extras.getDouble("latitude");
dstLongitude = extras.getDouble("longitude");
- if (name != null && name.length() > 0) {
- if (title != null && title.length() > 0) {
+ if (StringUtils.isNotBlank(name)) {
+ if (StringUtils.isNotBlank(title)) {
title = title + ": " + name;
} else {
title = name;
@@ -101,9 +103,9 @@ public class cgeonavigate extends AbstractActivity {
return;
}
- if (title != null && title.length() > 0) {
+ if (StringUtils.isNotBlank(title)) {
app.setAction(title);
- } else if (name != null && name.length() > 0) {
+ } else if (StringUtils.isNotBlank(name)) {
app.setAction(name);
}
@@ -132,9 +134,9 @@ public class cgeonavigate extends AbstractActivity {
settings.load();
- if (title != null && title.length() > 0) {
+ if (StringUtils.isNotBlank(title)) {
app.setAction(title);
- } else if (name != null && name.length() > 0) {
+ } else if (StringUtils.isNotBlank(name)) {
app.setAction(name);
}
@@ -295,7 +297,7 @@ public class cgeonavigate extends AbstractActivity {
}
private void setTitle() {
- if (title != null && title.length() > 0) {
+ if (StringUtils.isNotBlank(title)) {
setTitle(title);
} else {
setTitle(res.getString(R.string.navigation));
diff --git a/src/cgeo/geocaching/cgeopoint.java b/src/cgeo/geocaching/cgeopoint.java
index c3c0b23..d5f6434 100644
--- a/src/cgeo/geocaching/cgeopoint.java
+++ b/src/cgeo/geocaching/cgeopoint.java
@@ -6,6 +6,8 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -485,13 +487,13 @@ public class cgeopoint extends AbstractActivity {
String latText = ((EditText) findViewById(R.id.latitude)).getText().toString();
String lonText = ((EditText) findViewById(R.id.longitude)).getText().toString();
- if ((bearingText == null || bearingText.length() == 0) && (distanceText == null || distanceText.length() == 0)
- && (latText == null || latText.length() == 0) && (lonText == null || lonText.length() == 0)) {
+ if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText)
+ && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) {
showToast(res.getString(R.string.err_point_no_position_given));
return null;
}
- if (latText != null && latText.length() > 0 && lonText != null && lonText.length() > 0) {
+ if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) {
// latitude & longitude
HashMap<String, Object> latParsed = cgBase.parseCoordinate(latText, "lat");
HashMap<String, Object> lonParsed = cgBase.parseCoordinate(lonText, "lon");
@@ -518,7 +520,7 @@ public class cgeopoint extends AbstractActivity {
longitude = geo.longitudeNow;
}
- if (bearingText != null && bearingText.length() > 0 && distanceText != null && distanceText.length() > 0) {
+ if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) {
// bearing & distance
Double bearing = null;
try {
diff --git a/src/cgeo/geocaching/cgeopopup.java b/src/cgeo/geocaching/cgeopopup.java
index 5e78531..701f576 100644
--- a/src/cgeo/geocaching/cgeopopup.java
+++ b/src/cgeo/geocaching/cgeopopup.java
@@ -3,6 +3,8 @@ package cgeo.geocaching;
import java.util.HashMap;
import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
@@ -119,7 +121,7 @@ public class cgeopopup extends AbstractActivity {
geocode = extras.getString("geocode");
}
- if (geocode == null || geocode.length() == 0) {
+ if (StringUtils.isBlank(geocode)) {
showToast(res.getString(R.string.err_detail_cache_find));
finish();
@@ -231,7 +233,7 @@ public class cgeopopup extends AbstractActivity {
gcIcons.put("gchq", R.drawable.type_hq);
}
- if (cache.name != null && cache.name.length() > 0) {
+ if (StringUtils.isNotBlank(cache.name)) {
setTitle(cache.name);
} else {
setTitle(geocode.toUpperCase());
@@ -258,13 +260,13 @@ public class cgeopopup extends AbstractActivity {
itemName.setText(res.getString(R.string.cache_type));
if (cgBase.cacheTypesInv.containsKey(cache.type)) { // cache icon
- if (cache.size != null && cache.size.length() > 0) {
+ if (StringUtils.isNotBlank(cache.size)) {
itemValue.setText(cgBase.cacheTypesInv.get(cache.type) + " (" + cache.size + ")");
} else {
itemValue.setText(cgBase.cacheTypesInv.get(cache.type));
}
} else {
- if (cache.size != null && cache.size.length() > 0) {
+ if (StringUtils.isNotBlank(cache.size)) {
itemValue.setText(cgBase.cacheTypesInv.get("mystery") + " (" + cache.size + ")");
} else {
itemValue.setText(cgBase.cacheTypesInv.get("mystery"));
diff --git a/src/cgeo/geocaching/cgeosmaps.java b/src/cgeo/geocaching/cgeosmaps.java
index 35d09d0..ddc9732 100644
--- a/src/cgeo/geocaching/cgeosmaps.java
+++ b/src/cgeo/geocaching/cgeosmaps.java
@@ -13,6 +13,7 @@ import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import cgeo.geocaching.activity.AbstractActivity;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeosmaps extends AbstractActivity {
@@ -27,7 +28,7 @@ public class cgeosmaps extends AbstractActivity {
@Override
public void handleMessage(Message msg) {
try {
- if (maps == null || maps.isEmpty()) {
+ if (CollectionUtils.isEmpty(maps)) {
if (waitDialog != null) {
waitDialog.dismiss();
}
diff --git a/src/cgeo/geocaching/cgeotouch.java b/src/cgeo/geocaching/cgeotouch.java
index a085255..fed64ae 100644
--- a/src/cgeo/geocaching/cgeotouch.java
+++ b/src/cgeo/geocaching/cgeotouch.java
@@ -4,6 +4,9 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.res.Configuration;
@@ -46,7 +49,7 @@ public class cgeotouch extends cgLogForm {
private Handler loadDataHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- if (cgBase.isEmpty(viewstates) && attempts < 2) {
+ if (ArrayUtils.isEmpty(viewstates) && attempts < 2) {
showToast(res.getString(R.string.err_log_load_data_again));
loadData thread;
@@ -54,7 +57,7 @@ public class cgeotouch extends cgLogForm {
thread.start();
return;
- } else if (cgBase.isEmpty(viewstates) && attempts >= 2) {
+ } else if (ArrayUtils.isEmpty(viewstates) && attempts >= 2) {
showToast(res.getString(R.string.err_log_load_data));
showProgress(false);
@@ -285,7 +288,7 @@ public class cgeotouch extends cgLogForm {
tweetCheck.setChecked(true);
Button buttonPost = (Button)findViewById(R.id.post);
- if (cgBase.isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
buttonPost.setEnabled(false);
buttonPost.setOnTouchListener(null);
buttonPost.setOnClickListener(null);
@@ -365,7 +368,7 @@ public class cgeotouch extends cgLogForm {
attempts ++;
try {
- if (guid != null && guid.length() > 0) {
+ if (StringUtils.isNotBlank(guid)) {
params.put("wid", guid);
} else {
loadDataHandler.sendEmptyMessage(0);
diff --git a/src/cgeo/geocaching/cgeotrackable.java b/src/cgeo/geocaching/cgeotrackable.java
index d0f82be..c96b344 100644
--- a/src/cgeo/geocaching/cgeotrackable.java
+++ b/src/cgeo/geocaching/cgeotrackable.java
@@ -4,6 +4,8 @@ import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
@@ -51,7 +53,7 @@ public class cgeotrackable extends AbstractActivity {
return;
}
- if (trackable != null && trackable.error.length() > 0) {
+ if (trackable != null && StringUtils.isNotBlank(trackable.error)) {
showToast(res.getString(R.string.err_tb_details_download) + " " + trackable.error + ".");
finish();
@@ -63,7 +65,7 @@ public class cgeotrackable extends AbstractActivity {
waitDialog.dismiss();
}
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
showToast(res.getString(R.string.err_tb_find) + " " + geocode + ".");
} else {
showToast(res.getString(R.string.err_tb_find_that));
@@ -77,7 +79,7 @@ public class cgeotrackable extends AbstractActivity {
inflater = getLayoutInflater();
geocode = trackable.geocode.toUpperCase();
- if (trackable.name != null && trackable.name.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.name)) {
setTitle(Html.fromHtml(trackable.name).toString());
} else {
setTitle(trackable.name.toUpperCase());
@@ -87,7 +89,7 @@ public class cgeotrackable extends AbstractActivity {
LinearLayout detailsList = (LinearLayout) findViewById(R.id.details_list);
// actiobar icon
- if (trackable.iconUrl != null && trackable.iconUrl.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.iconUrl)) {
final tbIconHandler iconHandler = new tbIconHandler(((TextView) findViewById(R.id.actionbar_title)));
final tbIconThread iconThread = new tbIconThread(trackable.iconUrl, iconHandler);
iconThread.start();
@@ -99,7 +101,7 @@ public class cgeotrackable extends AbstractActivity {
itemValue = (TextView) itemLayout.findViewById(R.id.value);
itemName.setText(res.getString(R.string.trackable_name));
- if (trackable.name != null) {
+ if (StringUtils.isNotBlank(trackable.name)) {
itemValue.setText(Html.fromHtml(trackable.name).toString());
} else {
itemValue.setText(res.getString(R.string.trackable_unknown));
@@ -112,7 +114,7 @@ public class cgeotrackable extends AbstractActivity {
itemValue = (TextView) itemLayout.findViewById(R.id.value);
String tbType = null;
- if (trackable.type != null && trackable.type.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.type)) {
tbType = Html.fromHtml(trackable.type).toString();
} else {
tbType = res.getString(R.string.trackable_unknown);
@@ -136,7 +138,7 @@ public class cgeotrackable extends AbstractActivity {
itemValue = (TextView) itemLayout.findViewById(R.id.value);
itemName.setText(res.getString(R.string.trackable_owner));
- if (trackable.owner != null) {
+ if (StringUtils.isNotBlank(trackable.owner)) {
itemValue.setText(Html.fromHtml(trackable.owner), TextView.BufferType.SPANNABLE);
itemLayout.setOnClickListener(new userActions());
} else {
@@ -145,10 +147,9 @@ public class cgeotrackable extends AbstractActivity {
detailsList.addView(itemLayout);
// trackable spotted
- if (
- (trackable.spottedName != null && trackable.spottedName.length() > 0) ||
- trackable.spottedType == cgTrackable.SPOTTED_UNKNOWN ||
- trackable.spottedType == cgTrackable.SPOTTED_OWNER
+ if (StringUtils.isNotBlank(trackable.spottedName) ||
+ trackable.spottedType == cgTrackable.SPOTTED_UNKNOWN ||
+ trackable.spottedType == cgTrackable.SPOTTED_OWNER
) {
itemLayout = (RelativeLayout)inflater.inflate(R.layout.cache_item, null);
itemName = (TextView) itemLayout.findViewById(R.id.name);
@@ -189,7 +190,7 @@ public class cgeotrackable extends AbstractActivity {
}
// trackable origin
- if (trackable.origin != null && trackable.origin.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.origin)) {
itemLayout = (RelativeLayout)inflater.inflate(R.layout.cache_item, null);
itemName = (TextView) itemLayout.findViewById(R.id.name);
itemValue = (TextView) itemLayout.findViewById(R.id.value);
@@ -223,7 +224,7 @@ public class cgeotrackable extends AbstractActivity {
// trackable goal
- if (trackable.goal != null && trackable.goal.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.goal)) {
((LinearLayout) findViewById(R.id.goal_box)).setVisibility(View.VISIBLE);
TextView descView = (TextView) findViewById(R.id.goal);
descView.setVisibility(View.VISIBLE);
@@ -232,7 +233,7 @@ public class cgeotrackable extends AbstractActivity {
}
// trackable details
- if (trackable.details != null && trackable.details.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.details)) {
((LinearLayout) findViewById(R.id.details_box)).setVisibility(View.VISIBLE);
TextView descView = (TextView) findViewById(R.id.details);
descView.setVisibility(View.VISIBLE);
@@ -241,7 +242,7 @@ public class cgeotrackable extends AbstractActivity {
}
// trackable image
- if (trackable.image != null && trackable.image.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.image)) {
((LinearLayout) findViewById(R.id.image_box)).setVisibility(View.VISIBLE);
LinearLayout imgView = (LinearLayout) findViewById(R.id.image);
@@ -331,15 +332,15 @@ public class cgeotrackable extends AbstractActivity {
guid = uri.getQueryParameter("guid");
id = uri.getQueryParameter("id");
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
geocode = geocode.toUpperCase();
guid = null;
id = null;
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
geocode = null;
guid = guid.toLowerCase();
id = null;
- } else if (id != null && id.length() > 0) {
+ } else if (StringUtils.isNotBlank(id)) {
geocode = null;
guid = null;
id = id.toLowerCase();
@@ -369,9 +370,9 @@ public class cgeotrackable extends AbstractActivity {
return;
}
- if (name != null && name.length() > 0) {
+ if (StringUtils.isNotBlank(name)) {
waitDialog = ProgressDialog.show(this, Html.fromHtml(name).toString(), res.getString(R.string.trackable_details_loading), true);
- } else if (geocode != null && geocode.length() > 0) {
+ } else if (StringUtils.isNotBlank(geocode)) {
waitDialog = ProgressDialog.show(this, geocode.toUpperCase(), res.getString(R.string.trackable_details_loading), true);
} else {
waitDialog = ProgressDialog.show(this, res.getString(R.string.trackable), res.getString(R.string.trackable_details_loading), true);
@@ -500,11 +501,11 @@ public class cgeotrackable extends AbstractActivity {
public void loadTrackableFn(String geocode, String guid, String id) {
HashMap<String, String> params = new HashMap<String, String>();
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
params.put("geocode", geocode);
- } else if (guid != null && guid.length() > 0) {
+ } else if (StringUtils.isNotBlank(guid)) {
params.put("guid", guid);
- } else if (id != null && id.length() > 0) {
+ } else if (StringUtils.isNotBlank(id)) {
params.put("id", id);
} else {
return;
@@ -536,7 +537,7 @@ public class cgeotrackable extends AbstractActivity {
}
((TextView) rowView.findViewById(R.id.author)).setText(Html.fromHtml(log.author), TextView.BufferType.SPANNABLE);
- if (log.cacheName == null || log.cacheName.length() == 0) {
+ if (StringUtils.isBlank(log.cacheName)) {
((TextView) rowView.findViewById(R.id.location)).setVisibility(View.GONE);
} else {
((TextView) rowView.findViewById(R.id.location)).setText(Html.fromHtml(log.cacheName));
diff --git a/src/cgeo/geocaching/cgeovisit.java b/src/cgeo/geocaching/cgeovisit.java
index c26dba4..924ea7f 100644
--- a/src/cgeo/geocaching/cgeovisit.java
+++ b/src/cgeo/geocaching/cgeovisit.java
@@ -6,6 +6,9 @@ import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Intent;
@@ -26,6 +29,7 @@ import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import cgeo.geocaching.LogTemplateProvider.LogTemplate;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeovisit extends cgLogForm {
static final String EXTRAS_FOUND = "found";
@@ -79,7 +83,7 @@ public class cgeovisit extends cgLogForm {
showToast(res.getString(R.string.info_log_type_changed));
}
- if (cgBase.isEmpty(viewstates) && attempts < 2) {
+ if (ArrayUtils.isEmpty(viewstates) && attempts < 2) {
showToast(res.getString(R.string.err_log_load_data_again));
loadData thread;
@@ -87,7 +91,7 @@ public class cgeovisit extends cgLogForm {
thread.start();
return;
- } else if (cgBase.isEmpty(viewstates) && attempts >= 2) {
+ } else if (ArrayUtils.isEmpty(viewstates) && attempts >= 2) {
showToast(res.getString(R.string.err_log_load_data));
showProgress(false);
@@ -103,7 +107,7 @@ public class cgeovisit extends cgLogForm {
post.setOnClickListener(new postListener());
// add trackables
- if (trackables != null && trackables.isEmpty() == false) {
+ if (CollectionUtils.isNotEmpty(trackables)) {
if (inflater == null) {
inflater = getLayoutInflater();
}
@@ -234,16 +238,16 @@ public class cgeovisit extends cgLogForm {
alreadyFound = extras.getBoolean(EXTRAS_FOUND);
}
- if ((cacheid == null || cacheid.length() == 0) && geocode != null && geocode.length() > 0) {
+ if ((StringUtils.isBlank(cacheid)) && StringUtils.isNotBlank(geocode)) {
cacheid = app.getCacheid(geocode);
}
- if ((geocode == null || geocode.length() == 0) && cacheid != null && cacheid.length() > 0) {
+ if ((StringUtils.isBlank(geocode)) && StringUtils.isNotBlank(cacheid)) {
geocode = app.getGeocode(cacheid);
}
cache = app.getCacheByGeocode(geocode);
- if (cache.name != null && cache.name.length() > 0) {
+ if (StringUtils.isNotBlank(cache.name)) {
setTitle(res.getString(R.string.log_new_log) + " " + cache.name);
} else {
setTitle(res.getString(R.string.log_new_log) + " " + cache.geocode.toUpperCase());
@@ -305,7 +309,7 @@ public class cgeovisit extends cgLogForm {
boolean signatureAvailable = settings.getSignature() != null;
menu.findItem(MENU_SIGNATURE).setVisible(signatureAvailable);
- boolean voteAvailable = settings.isGCvoteLogin() && typeSelected == cgBase.LOG_FOUND_IT && cache.guid != null && cache.guid.length() > 0;
+ boolean voteAvailable = settings.isGCvoteLogin() && typeSelected == cgBase.LOG_FOUND_IT && StringUtils.isNotBlank(cache.guid);
menu.findItem(SUBMENU_VOTE).setVisible(voteAvailable);
return true;
@@ -318,7 +322,7 @@ public class cgeovisit extends cgLogForm {
if (id == MENU_SIGNATURE) {
EditText log = (EditText) findViewById(R.id.log);
String content = log.getText().toString();
- if (content.length() > 0) {
+ if (StringUtils.isNotBlank(content)) {
insertIntoLog("\n");
}
insertIntoLog(LogTemplateProvider.applyTemplates(settings.getSignature(), base));
@@ -355,7 +359,7 @@ public class cgeovisit extends cgLogForm {
}
public boolean setRating(String guid, double vote) {
- if (guid == null || guid.length() == 0) {
+ if (StringUtils.isBlank(guid)) {
return false;
}
if (vote < 0.0 || vote > 5.0) {
@@ -498,10 +502,9 @@ public class cgeovisit extends cgLogForm {
}
post.setText(res.getString(R.string.log_post_no_rate));
}
- } else if (settings.getSignature() != null
+ } else if (StringUtils.isNotBlank(settings.getSignature())
&& settings.signatureAutoinsert
- && settings.getSignature().length() > 0
- && 0 == ((EditText) findViewById(R.id.log)).getText().length()) {
+ && StringUtils.isBlank(((EditText) findViewById(R.id.log)).getText())) {
insertIntoLog(LogTemplateProvider.applyTemplates(settings.getSignature(), base));
}
@@ -529,7 +532,7 @@ public class cgeovisit extends cgLogForm {
dateButton.setOnClickListener(new cgeovisitDateListener());
EditText logView = (EditText) findViewById(R.id.log);
- if (logView.getText().length() == 0 && text != null && text.length() > 0) {
+ if (StringUtils.isBlank(logView.getText()) && StringUtils.isNotBlank(text) ) {
logView.setText(text);
}
@@ -545,7 +548,7 @@ public class cgeovisit extends cgLogForm {
if (post == null) {
post = (Button) findViewById(R.id.post);
}
- if (cgBase.isEmpty(viewstates)) {
+ if (ArrayUtils.isEmpty(viewstates)) {
post.setEnabled(false);
post.setOnTouchListener(null);
post.setOnClickListener(null);
@@ -670,7 +673,7 @@ public class cgeovisit extends cgLogForm {
dateButton.setOnClickListener(new cgeovisitDateListener());
EditText logView = (EditText) findViewById(R.id.log);
- if (text != null && text.length() > 0) {
+ if (StringUtils.isNotBlank(text)) {
logView.setText(text);
} else {
logView.setText("");
@@ -705,7 +708,7 @@ public class cgeovisit extends cgLogForm {
attempts++;
try {
- if (cacheid != null && cacheid.length() > 0) {
+ if (StringUtils.isNotBlank(cacheid)) {
params.put("ID", cacheid);
} else {
loadDataHandler.sendEmptyMessage(0);
@@ -718,7 +721,7 @@ public class cgeovisit extends cgLogForm {
trackables = cgBase.parseTrackableLog(page);
final ArrayList<Integer> typesPre = cgBase.parseTypes(page);
- if (typesPre.size() > 0) {
+ if (CollectionUtils.isNotEmpty(typesPre)) {
types.clear();
types.addAll(typesPre);
types.remove(Integer.valueOf(cgBase.LOG_UPDATE_COORDINATES));
@@ -799,8 +802,8 @@ public class cgeovisit extends cgLogForm {
if (
status == 1 && typeSelected == cgBase.LOG_FOUND_IT && settings.twitter == 1
- && settings.tokenPublic != null && settings.tokenPublic.length() > 0 && settings.tokenSecret != null
- && settings.tokenSecret.length() > 0 && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE
+ && StringUtils.isNotBlank(settings.tokenPublic) && StringUtils.isNotBlank(settings.tokenSecret)
+ && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE
) {
cgBase.postTweetCache(app, settings, geocode);
}
diff --git a/src/cgeo/geocaching/cgeowaypoint.java b/src/cgeo/geocaching/cgeowaypoint.java
index e4ec379..797b134 100644
--- a/src/cgeo/geocaching/cgeowaypoint.java
+++ b/src/cgeo/geocaching/cgeowaypoint.java
@@ -2,6 +2,8 @@ package cgeo.geocaching;
import java.util.ArrayList;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
@@ -56,7 +58,7 @@ public class cgeowaypoint extends AbstractActivity {
final View headline = (View) findViewById(R.id.headline);
registerNavigationMenu(headline);
- if (waypoint.name != null && waypoint.name.length() > 0) {
+ if (StringUtils.isNotBlank(waypoint.name)) {
setTitle(Html.fromHtml(waypoint.name.trim()).toString());
} else {
setTitle(res.getString(R.string.waypoint_title));
@@ -81,7 +83,7 @@ public class cgeowaypoint extends AbstractActivity {
}
registerNavigationMenu(coords);
- if (waypoint.note != null && waypoint.note.length() > 0) {
+ if (StringUtils.isNotBlank(waypoint.note)) {
final TextView note = (TextView) findViewById(R.id.note);
note.setText(Html.fromHtml(waypoint.note.trim()), TextView.BufferType.SPANNABLE);
registerNavigationMenu(note);
diff --git a/src/cgeo/geocaching/cgeowaypointadd.java b/src/cgeo/geocaching/cgeowaypointadd.java
index 1860129..6caf14b 100644
--- a/src/cgeo/geocaching/cgeowaypointadd.java
+++ b/src/cgeo/geocaching/cgeowaypointadd.java
@@ -5,6 +5,8 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
@@ -94,7 +96,7 @@ public class cgeowaypointadd extends AbstractActivity {
id = extras.getInt("waypoint");
}
- if ((geocode == null || geocode.length() == 0) && id <= 0) {
+ if (StringUtils.isBlank(geocode) && id <= 0) {
showToast(res.getString(R.string.err_waypoint_cache_unknown));
finish();
@@ -245,13 +247,13 @@ public class cgeowaypointadd extends AbstractActivity {
final String latText = ((Button) findViewById(R.id.buttonLatitude)).getText().toString();
final String lonText = ((Button) findViewById(R.id.buttonLongitude)).getText().toString();
- if ((bearingText == null || bearingText.length() == 0) && (distanceText == null || distanceText.length() == 0)
- && (latText == null || latText.length() == 0) && (lonText == null || lonText.length() == 0)) {
+ if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)
+ && StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) {
helpDialog(res.getString(R.string.err_point_no_position_given_title), res.getString(R.string.err_point_no_position_given));
return;
}
- if (latText != null && latText.length() > 0 && lonText != null && lonText.length() > 0) {
+ if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) {
// latitude & longitude
HashMap<String, Object> latParsed = cgBase.parseCoordinate(latText, "lat");
HashMap<String, Object> lonParsed = cgBase.parseCoordinate(lonText, "lon");
@@ -278,7 +280,7 @@ public class cgeowaypointadd extends AbstractActivity {
longitude = geo.longitudeNow;
}
- if (bearingText != null && bearingText.length() > 0 && distanceText != null && distanceText.length() > 0) {
+ if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) {
// bearing & distance
Double bearing = null;
try {
diff --git a/src/cgeo/geocaching/connector/GCConnector.java b/src/cgeo/geocaching/connector/GCConnector.java
index 5d5de6b..1d61d2f 100644
--- a/src/cgeo/geocaching/connector/GCConnector.java
+++ b/src/cgeo/geocaching/connector/GCConnector.java
@@ -1,12 +1,14 @@
package cgeo.geocaching.connector;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
public class GCConnector extends AbstractConnector implements IConnector {
@Override
public boolean canHandle(String geocode) {
- return geocode != null && geocode.toUpperCase().startsWith("GC");
+ return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("GC");
}
@Override
diff --git a/src/cgeo/geocaching/connector/OCConnector.java b/src/cgeo/geocaching/connector/OCConnector.java
index d23b3cd..8d1c42f 100644
--- a/src/cgeo/geocaching/connector/OCConnector.java
+++ b/src/cgeo/geocaching/connector/OCConnector.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.connector;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
/**
@@ -9,7 +11,7 @@ import cgeo.geocaching.cgCache;
public class OCConnector extends AbstractConnector implements IConnector {
@Override
public boolean canHandle(String geocode) {
- return geocode != null && geocode.toUpperCase().startsWith("OC");
+ return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("OC");
}
@Override
diff --git a/src/cgeo/geocaching/connector/OXConnector.java b/src/cgeo/geocaching/connector/OXConnector.java
index 75d404e..dc78044 100644
--- a/src/cgeo/geocaching/connector/OXConnector.java
+++ b/src/cgeo/geocaching/connector/OXConnector.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.connector;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
/**
@@ -10,7 +12,7 @@ public class OXConnector extends AbstractConnector implements IConnector {
@Override
public boolean canHandle(String geocode) {
- return geocode != null && geocode.toUpperCase().startsWith("OX");
+ return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("OX");
}
@Override
diff --git a/src/cgeo/geocaching/files/FileList.java b/src/cgeo/geocaching/files/FileList.java
index 67e89a0..be808a3 100644
--- a/src/cgeo/geocaching/files/FileList.java
+++ b/src/cgeo/geocaching/files/FileList.java
@@ -3,6 +3,8 @@ package cgeo.geocaching.files;
import java.io.File;
import java.util.ArrayList;
+import org.apache.commons.lang3.ArrayUtils;
+
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -185,7 +187,7 @@ public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractLis
final File[] files = directory.listFiles();
- if (files != null && files.length > 0) {
+ if (ArrayUtils.isNotEmpty(files)) {
for (File file : files) {
if (endSearching) {
return;
diff --git a/src/cgeo/geocaching/files/GPXParser.java b/src/cgeo/geocaching/files/GPXParser.java
index 2a3b167..881f186 100644
--- a/src/cgeo/geocaching/files/GPXParser.java
+++ b/src/cgeo/geocaching/files/GPXParser.java
@@ -11,6 +11,7 @@ import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
@@ -112,14 +113,14 @@ public abstract class GPXParser extends FileParser {
@Override
public void end() {
- if (cache.geocode == null || cache.geocode.length() == 0) {
+ if (StringUtils.isBlank(cache.geocode)) {
// try to find geocode somewhere else
findGeoCode(name);
findGeoCode(desc);
findGeoCode(cmt);
}
- if (cache.geocode != null && cache.geocode.length() > 0
+ if (StringUtils.isNotBlank(cache.geocode)
&& cache.latitude != null && cache.longitude != null
&& ((type == null && sym == null)
|| (type != null && type.indexOf("geocache") > -1)
@@ -318,7 +319,7 @@ public abstract class GPXParser extends FileParser {
@Override
public void end(String body) {
- if (cache.location == null || cache.location.length() == 0) {
+ if (StringUtils.isBlank(cache.location)) {
cache.location = validate(body.trim());
} else {
cache.location = cache.location + ", " + body.trim();
@@ -331,7 +332,7 @@ public abstract class GPXParser extends FileParser {
@Override
public void end(String body) {
- if (cache.location == null || cache.location.length() == 0) {
+ if (StringUtils.isBlank(cache.location)) {
cache.location = validate(body.trim());
} else {
cache.location = body.trim() + ", " + cache.location;
@@ -434,7 +435,7 @@ public abstract class GPXParser extends FileParser {
@Override
public void end() {
- if (trackable.geocode != null && trackable.geocode.length() > 0 && trackable.name != null && trackable.name.length() > 0) {
+ if (StringUtils.isNotBlank(trackable.geocode) && StringUtils.isNotBlank(trackable.name)) {
if (cache.inventory == null) {
cache.inventory = new ArrayList<cgTrackable>();
}
@@ -478,7 +479,7 @@ public abstract class GPXParser extends FileParser {
@Override
public void end() {
- if (log.log != null && log.log.length() > 0) {
+ if (StringUtils.isNotBlank(log.log)) {
if (cache.logs == null) {
cache.logs = new ArrayList<cgLog>();
}
@@ -572,14 +573,14 @@ public abstract class GPXParser extends FileParser {
cache.type = knownType;
}
else {
- if (cache.type == null || cache.type.length() == 0) {
+ if (StringUtils.isBlank(cache.type)) {
cache.type = "mystery"; // default for not recognized types
}
}
}
private void findGeoCode(final String input) {
- if (input == null || (cache.geocode != null && cache.geocode.length() != 0)) {
+ if (input == null || StringUtils.isNotBlank(cache.geocode)) {
return;
}
Matcher matcherGeocode = patternGeocode.matcher(input);
@@ -612,4 +613,4 @@ public abstract class GPXParser extends FileParser {
return search.getCurrentId();
}
-}
+} \ No newline at end of file
diff --git a/src/cgeo/geocaching/files/LocParser.java b/src/cgeo/geocaching/files/LocParser.java
index 152293c..da21ed2 100644
--- a/src/cgeo/geocaching/files/LocParser.java
+++ b/src/cgeo/geocaching/files/LocParser.java
@@ -6,6 +6,8 @@ import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.os.Handler;
import android.util.Log;
import cgeo.geocaching.cgBase;
@@ -53,7 +55,7 @@ public final class LocParser extends FileParser {
cache.terrain = coord.terrain;
cache.size = coord.size;
cache.geocode = coord.geocode.toUpperCase();
- if (cache.name == null || cache.name.length() == 0) {
+ if (StringUtils.isBlank(cache.name)) {
cache.name = coord.name;
}
}
@@ -61,7 +63,7 @@ public final class LocParser extends FileParser {
private static HashMap<String, cgCoord> parseCoordinates(
final String fileContent) {
final HashMap<String, cgCoord> coords = new HashMap<String, cgCoord>();
- if (fileContent == null || fileContent.length() <= 0) {
+ if (StringUtils.isBlank(fileContent)) {
return coords;
}
// >> premium only
@@ -131,7 +133,7 @@ public final class LocParser extends FileParser {
}
}
- if (pointCoord.geocode != null && pointCoord.geocode.length() > 0) {
+ if (StringUtils.isNotBlank(pointCoord.geocode)) {
coords.put(pointCoord.geocode, pointCoord);
}
}
@@ -151,7 +153,7 @@ public final class LocParser extends FileParser {
final cgCacheWrap caches = new cgCacheWrap();
for (Entry<String, cgCoord> entry : coords.entrySet()) {
cgCoord coord = entry.getValue();
- if (coord.geocode == null || coord.geocode.length() == 0 || coord.name == null || coord.name.length() == 0) {
+ if (StringUtils.isBlank(coord.geocode) || StringUtils.isBlank(coord.name)) {
continue;
}
cgCache cache = new cgCache();
diff --git a/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java b/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java
index f2ba50d..60439be 100644
--- a/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java
+++ b/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -109,7 +111,7 @@ public class cgUsersOverlay extends ItemizedOverlayBase implements OverlayBase {
dialog.setTitle(user.username);
dialog.setMessage(action);
dialog.setCancelable(true);
- if (geocode != null && geocode.length() > 0) {
+ if (StringUtils.isNotBlank(geocode)) {
dialog.setPositiveButton(geocode + "?", new cacheDetails(geocode));
}
dialog.setNeutralButton("Dismiss", new DialogInterface.OnClickListener() {
diff --git a/src/cgeo/geocaching/mapcommon/cgeomap.java b/src/cgeo/geocaching/mapcommon/cgeomap.java
index 8500e1c..8fbf2bc 100644
--- a/src/cgeo/geocaching/mapcommon/cgeomap.java
+++ b/src/cgeo/geocaching/mapcommon/cgeomap.java
@@ -28,13 +28,13 @@ import cgeo.geocaching.cgCoord;
import cgeo.geocaching.cgDirection;
import cgeo.geocaching.cgGeo;
import cgeo.geocaching.cgSettings;
+import cgeo.geocaching.cgSettings.mapSourceEnum;
import cgeo.geocaching.cgUpdateDir;
import cgeo.geocaching.cgUpdateLoc;
import cgeo.geocaching.cgUser;
import cgeo.geocaching.cgWaypoint;
import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.activity.ActivityMixin;
-import cgeo.geocaching.cgSettings.mapSourceEnum;
import cgeo.geocaching.mapinterfaces.ActivityImpl;
import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl;
import cgeo.geocaching.mapinterfaces.GeoPointImpl;
@@ -42,6 +42,7 @@ import cgeo.geocaching.mapinterfaces.MapControllerImpl;
import cgeo.geocaching.mapinterfaces.MapFactory;
import cgeo.geocaching.mapinterfaces.MapViewImpl;
import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl;
+import cgeo.geocaching.utils.CollectionUtils;
public class cgeomap extends MapBase {
@@ -502,7 +503,7 @@ public class cgeomap extends MapBase {
}
item = menu.findItem(MENU_STORE_CACHES); // store loaded
- if (live && !isLoading() && app.getNotOfflineCount(searchId) > 0 && caches != null && caches.size() > 0) {
+ if (live && !isLoading() && app.getNotOfflineCount(searchId) > 0 && CollectionUtils.isNotEmpty(caches)) {
item.setEnabled(true);
} else {
item.setEnabled(false);
@@ -559,7 +560,7 @@ public class cgeomap extends MapBase {
ArrayList<cgCache> cachesProtected = new ArrayList<cgCache>(caches);
try {
- if (cachesProtected.size() > 0) {
+ if (CollectionUtils.isNotEmpty(cachesProtected)) {
final GeoPointImpl mapCenter = mapView.getMapViewCenter();
final int mapCenterLat = mapCenter.getLatitudeE6();
final int mapCenterLon = mapCenter.getLongitudeE6();
diff --git a/src/cgeo/geocaching/sorting/GeocodeComparator.java b/src/cgeo/geocaching/sorting/GeocodeComparator.java
index dd16f08..957545b 100644
--- a/src/cgeo/geocaching/sorting/GeocodeComparator.java
+++ b/src/cgeo/geocaching/sorting/GeocodeComparator.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.sorting;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
/**
@@ -10,8 +12,8 @@ public class GeocodeComparator extends AbstractCacheComparator {
@Override
protected boolean canCompare(cgCache cache1, cgCache cache2) {
- return cache1.geocode != null && cache1.geocode.length() > 0
- && cache2.geocode != null && cache2.geocode.length() > 0;
+ return StringUtils.isNotBlank(cache1.geocode)
+ && StringUtils.isNotBlank(cache2.geocode);
}
@Override
diff --git a/src/cgeo/geocaching/sorting/NameComparator.java b/src/cgeo/geocaching/sorting/NameComparator.java
index f1c5ae3..9f72258 100644
--- a/src/cgeo/geocaching/sorting/NameComparator.java
+++ b/src/cgeo/geocaching/sorting/NameComparator.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.sorting;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
/**
@@ -10,7 +12,7 @@ public class NameComparator extends AbstractCacheComparator {
@Override
protected boolean canCompare(cgCache cache1, cgCache cache2) {
- return cache1.name != null && cache2.name != null;
+ return StringUtils.isNotBlank(cache1.name) && StringUtils.isNotBlank(cache2.name);
}
@Override
diff --git a/src/cgeo/geocaching/sorting/SizeComparator.java b/src/cgeo/geocaching/sorting/SizeComparator.java
index dd9d448..cff5c49 100644
--- a/src/cgeo/geocaching/sorting/SizeComparator.java
+++ b/src/cgeo/geocaching/sorting/SizeComparator.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.sorting;
+import org.apache.commons.lang3.StringUtils;
+
import cgeo.geocaching.cgCache;
/**
@@ -10,7 +12,7 @@ public class SizeComparator extends AbstractCacheComparator {
@Override
protected boolean canCompare(cgCache cache1, cgCache cache2) {
- return cache1.size != null && cache1.size.length() > 0 && cache2.size != null && cache2.size.length() > 0;
+ return StringUtils.isNotBlank(cache1.size) && StringUtils.isNotBlank(cache2.size);
}
@Override
diff --git a/src/cgeo/geocaching/utils/CollectionUtils.java b/src/cgeo/geocaching/utils/CollectionUtils.java
new file mode 100644
index 0000000..89b4b51
--- /dev/null
+++ b/src/cgeo/geocaching/utils/CollectionUtils.java
@@ -0,0 +1,25 @@
+package cgeo.geocaching.utils;
+
+import java.util.List;
+import java.util.Map;
+
+public class CollectionUtils {
+
+ public static <T> boolean isEmpty(List<T> list) {
+ return (list != null && list.size() == 0);
+ }
+ public static <T,T2> boolean isEmpty(Map<T,T2> map) {
+ return (map != null && map.size() == 0);
+ }
+
+
+ public static <T> boolean isNotEmpty(List<T> list) {
+ return (list != null && list.size() != 0);
+ }
+
+ public static <T,T2> boolean isNotEmpty(Map<T,T2> map) {
+ return (map != null && map.size() != 0);
+ }
+
+
+}
diff --git a/src/org/apache/commons/lang3/ArrayUtils.java b/src/org/apache/commons/lang3/ArrayUtils.java
new file mode 100644
index 0000000..352d7e0
--- /dev/null
+++ b/src/org/apache/commons/lang3/ArrayUtils.java
@@ -0,0 +1,5796 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.lang3.mutable.MutableInt;
+
+/**
+ * <p>Operations on arrays, primitive arrays (like {@code int[]}) and
+ * primitive wrapper arrays (like {@code Integer[]}).</p>
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null}
+ * array input. However, an Object array that contains a {@code null}
+ * element may throw an exception. Each method documents its behaviour.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 2.0
+ * @version $Id$
+ */
+public class ArrayUtils {
+
+ /**
+ * An empty immutable {@code Object} array.
+ */
+ public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+ /**
+ * An empty immutable {@code Class} array.
+ */
+ public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0];
+ /**
+ * An empty immutable {@code String} array.
+ */
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+ /**
+ * An empty immutable {@code long} array.
+ */
+ public static final long[] EMPTY_LONG_ARRAY = new long[0];
+ /**
+ * An empty immutable {@code Long} array.
+ */
+ public static final Long[] EMPTY_LONG_OBJECT_ARRAY = new Long[0];
+ /**
+ * An empty immutable {@code int} array.
+ */
+ public static final int[] EMPTY_INT_ARRAY = new int[0];
+ /**
+ * An empty immutable {@code Integer} array.
+ */
+ public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0];
+ /**
+ * An empty immutable {@code short} array.
+ */
+ public static final short[] EMPTY_SHORT_ARRAY = new short[0];
+ /**
+ * An empty immutable {@code Short} array.
+ */
+ public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = new Short[0];
+ /**
+ * An empty immutable {@code byte} array.
+ */
+ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ /**
+ * An empty immutable {@code Byte} array.
+ */
+ public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = new Byte[0];
+ /**
+ * An empty immutable {@code double} array.
+ */
+ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+ /**
+ * An empty immutable {@code Double} array.
+ */
+ public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0];
+ /**
+ * An empty immutable {@code float} array.
+ */
+ public static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+ /**
+ * An empty immutable {@code Float} array.
+ */
+ public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = new Float[0];
+ /**
+ * An empty immutable {@code boolean} array.
+ */
+ public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
+ /**
+ * An empty immutable {@code Boolean} array.
+ */
+ public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = new Boolean[0];
+ /**
+ * An empty immutable {@code char} array.
+ */
+ public static final char[] EMPTY_CHAR_ARRAY = new char[0];
+ /**
+ * An empty immutable {@code Character} array.
+ */
+ public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = new Character[0];
+
+ /**
+ * The index value when an element is not found in a list or array: {@code -1}.
+ * This value is returned by methods in this class and can also be used in comparisons with values returned by
+ * various method from {@link java.util.List}.
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * <p>ArrayUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as <code>ArrayUtils.clone(new int[] {2})</code>.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public ArrayUtils() {
+ super();
+ }
+
+
+ // NOTE: Cannot use {@code} to enclose text which includes {}, but <code></code> is OK
+
+
+ // Basic methods handling multi-dimensional arrays
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Outputs an array as a String, treating {@code null} as an empty array.</p>
+ *
+ * <p>Multi-dimensional arrays are handled correctly, including
+ * multi-dimensional primitive arrays.</p>
+ *
+ * <p>The format is that of Java source code, for example <code>{a,b}</code>.</p>
+ *
+ * @param array the array to get a toString for, may be {@code null}
+ * @return a String representation of the array, '{}' if null array input
+ */
+ public static String toString(Object array) {
+ return toString(array, "{}");
+ }
+
+ /**
+ * <p>Outputs an array as a String handling {@code null}s.</p>
+ *
+ * <p>Multi-dimensional arrays are handled correctly, including
+ * multi-dimensional primitive arrays.</p>
+ *
+ * <p>The format is that of Java source code, for example <code>{a,b}</code>.</p>
+ *
+ * @param array the array to get a toString for, may be {@code null}
+ * @param stringIfNull the String to return if the array is {@code null}
+ * @return a String representation of the array
+ */
+ public static String toString(Object array, String stringIfNull) {
+ if (array == null) {
+ return stringIfNull;
+ }
+ return new ToStringBuilder(array, ToStringStyle.SIMPLE_STYLE).append(array).toString();
+ }
+
+ /**
+ * <p>Get a hash code for an array handling multi-dimensional arrays correctly.</p>
+ *
+ * <p>Multi-dimensional primitive arrays are also handled correctly by this method.</p>
+ *
+ * @param array the array to get a hash code for, {@code null} returns zero
+ * @return a hash code for the array
+ */
+ public static int hashCode(Object array) {
+ return new HashCodeBuilder().append(array).toHashCode();
+ }
+
+ /**
+ * <p>Compares two arrays, using equals(), handling multi-dimensional arrays
+ * correctly.</p>
+ *
+ * <p>Multi-dimensional primitive arrays are also handled correctly by this method.</p>
+ *
+ * @param array1 the left hand array to compare, may be {@code null}
+ * @param array2 the right hand array to compare, may be {@code null}
+ * @return {@code true} if the arrays are equal
+ */
+ public static boolean isEquals(Object array1, Object array2) {
+ return new EqualsBuilder().append(array1, array2).isEquals();
+ }
+
+ // To map
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the given array into a {@link java.util.Map}. Each element of the array
+ * must be either a {@link java.util.Map.Entry} or an Array, containing at least two
+ * elements, where the first element is used as key and the second as
+ * value.</p>
+ *
+ * <p>This method can be used to initialize:</p>
+ * <pre>
+ * // Create a Map mapping colors.
+ * Map colorMap = MapUtils.toMap(new String[][] {{
+ * {"RED", "#FF0000"},
+ * {"GREEN", "#00FF00"},
+ * {"BLUE", "#0000FF"}});
+ * </pre>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array an array whose elements are either a {@link java.util.Map.Entry} or
+ * an Array containing at least two elements, may be {@code null}
+ * @return a {@code Map} that was created from the array
+ * @throws IllegalArgumentException if one element of this Array is
+ * itself an Array containing less then two elements
+ * @throws IllegalArgumentException if the array contains elements other
+ * than {@link java.util.Map.Entry} and an Array
+ */
+ public static Map<Object, Object> toMap(Object[] array) {
+ if (array == null) {
+ return null;
+ }
+ final Map<Object, Object> map = new HashMap<Object, Object>((int) (array.length * 1.5));
+ for (int i = 0; i < array.length; i++) {
+ Object object = array[i];
+ if (object instanceof Map.Entry<?, ?>) {
+ Map.Entry<?,?> entry = (Map.Entry<?,?>) object;
+ map.put(entry.getKey(), entry.getValue());
+ } else if (object instanceof Object[]) {
+ Object[] entry = (Object[]) object;
+ if (entry.length < 2) {
+ throw new IllegalArgumentException("Array element " + i + ", '"
+ + object
+ + "', has a length less than 2");
+ }
+ map.put(entry[0], entry[1]);
+ } else {
+ throw new IllegalArgumentException("Array element " + i + ", '"
+ + object
+ + "', is neither of type Map.Entry nor an Array");
+ }
+ }
+ return map;
+ }
+
+ // Generic array
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Create a type-safe generic array.</p>
+ *
+ * <p>The Java language does not allow an array to be created from a generic type:</p>
+ *
+ * <pre>
+ public static &lt;T&gt; T[] createAnArray(int size) {
+ return new T[size]; // compiler error here
+ }
+ public static &lt;T&gt; T[] createAnArray(int size) {
+ return (T[])new Object[size]; // ClassCastException at runtime
+ }
+ * </pre>
+ *
+ * <p>Therefore new arrays of generic types can be created with this method.
+ * For example, an array of Strings can be created:</p>
+ *
+ * <pre>
+ String[] array = ArrayUtils.toArray("1", "2");
+ String[] emptyArray = ArrayUtils.&lt;String&gt;toArray();
+ * </pre>
+ *
+ * <p>The method is typically used in scenarios, where the caller itself uses generic types
+ * that have to be combined into an array.</p>
+ *
+ * <p>Note, this method makes only sense to provide arguments of the same type so that the
+ * compiler can deduce the type of the array itself. While it is possible to select the
+ * type explicitly like in
+ * <code>Number[] array = ArrayUtils.&lt;Number&gt;toArray(Integer.valueOf(42), Double.valueOf(Math.PI))</code>,
+ * there is no real advantage when compared to
+ * <code>new Number[] {Integer.valueOf(42), Double.valueOf(Math.PI)}</code>.</p>
+ *
+ * @param <T> the array's element type
+ * @param items the varargs array items, null allowed
+ * @return the array, not null unless a null array is passed in
+ * @since 3.0
+ */
+ public static <T> T[] toArray(final T... items) {
+ return items;
+ }
+
+ // Clone
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Shallow clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>The objects in the array are not cloned, thus there is no special
+ * handling for multi-dimensional arrays.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to shallow clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static <T> T[] clone(T[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static long[] clone(long[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static int[] clone(int[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static short[] clone(short[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static char[] clone(char[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static byte[] clone(byte[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static double[] clone(double[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static float[] clone(float[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ /**
+ * <p>Clones an array returning a typecast result and handling
+ * {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static boolean[] clone(boolean[] array) {
+ if (array == null) {
+ return null;
+ }
+ return array.clone();
+ }
+
+ // nullToEmpty
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Object[] nullToEmpty(Object[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static String[] nullToEmpty(String[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_STRING_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static long[] nullToEmpty(long[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static int[] nullToEmpty(int[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static short[] nullToEmpty(short[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static char[] nullToEmpty(char[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static byte[] nullToEmpty(byte[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static double[] nullToEmpty(double[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static float[] nullToEmpty(float[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static boolean[] nullToEmpty(boolean[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Long[] nullToEmpty(Long[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_LONG_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Integer[] nullToEmpty(Integer[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_INTEGER_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Short[] nullToEmpty(Short[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_SHORT_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Character[] nullToEmpty(Character[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_CHARACTER_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Byte[] nullToEmpty(Byte[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_BYTE_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Double[] nullToEmpty(Double[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_DOUBLE_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Float[] nullToEmpty(Float[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_FLOAT_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ /**
+ * <p>Defensive programming technique to change a {@code null}
+ * reference to an empty one.</p>
+ *
+ * <p>This method returns an empty array for a {@code null} input array.</p>
+ *
+ * <p>As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.</p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Boolean[] nullToEmpty(Boolean[] array) {
+ if (array == null || array.length == 0) {
+ return EMPTY_BOOLEAN_OBJECT_ARRAY;
+ }
+ return array;
+ }
+
+ // Subarrays
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Produces a new array containing the elements between
+ * the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * <p>The component type of the subarray is always the same as
+ * that of the input array. Thus, if the input is an array of type
+ * {@code Date}, the following usage is envisaged:</p>
+ *
+ * <pre>
+ * Date[] someDates = (Date[])ArrayUtils.subarray(allDates, 2, 5);
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static <T> T[] subarray(T[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ Class<?> type = array.getClass().getComponentType();
+ if (newSize <= 0) {
+ @SuppressWarnings("unchecked") // OK, because array is of type T
+ final T[] emptyArray = (T[]) Array.newInstance(type, 0);
+ return emptyArray;
+ }
+ @SuppressWarnings("unchecked") // OK, because array is of type T
+ T[] subarray = (T[]) Array.newInstance(type, newSize);
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code long} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static long[] subarray(long[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+
+ long[] subarray = new long[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code int} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static int[] subarray(int[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_INT_ARRAY;
+ }
+
+ int[] subarray = new int[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code short} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static short[] subarray(short[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+
+ short[] subarray = new short[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code char} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static char[] subarray(char[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+
+ char[] subarray = new char[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code byte} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static byte[] subarray(byte[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ byte[] subarray = new byte[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code double} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static double[] subarray(double[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+
+ double[] subarray = new double[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code float} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static float[] subarray(float[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+
+ float[] subarray = new float[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * <p>Produces a new {@code boolean} array containing the elements
+ * between the start and end indices.</p>
+ *
+ * <p>The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.</p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ */
+ public static boolean[] subarray(boolean[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+
+ boolean[] subarray = new boolean[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ // Is same length
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * <p>Any multi-dimensional aspects of the arrays are ignored.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(Object[] array1, Object[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(long[] array1, long[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(int[] array1, int[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(short[] array1, short[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(char[] array1, char[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(byte[] array1, byte[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(double[] array1, double[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(float[] array1, float[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.</p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(boolean[] array1, boolean[] array2) {
+ if ((array1 == null && array2 != null && array2.length > 0) ||
+ (array2 == null && array1 != null && array1.length > 0) ||
+ (array1 != null && array2 != null && array1.length != array2.length)) {
+ return false;
+ }
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns the length of the specified array.
+ * This method can deal with {@code Object} arrays and with primitive arrays.</p>
+ *
+ * <p>If the input array is {@code null}, {@code 0} is returned.</p>
+ *
+ * <pre>
+ * ArrayUtils.getLength(null) = 0
+ * ArrayUtils.getLength([]) = 0
+ * ArrayUtils.getLength([null]) = 1
+ * ArrayUtils.getLength([true, false]) = 2
+ * ArrayUtils.getLength([1, 2, 3]) = 3
+ * ArrayUtils.getLength(["a", "b", "c"]) = 3
+ * </pre>
+ *
+ * @param array the array to retrieve the length from, may be null
+ * @return The length of the array, or {@code 0} if the array is {@code null}
+ * @throws IllegalArgumentException if the object arguement is not an array.
+ * @since 2.1
+ */
+ public static int getLength(Object array) {
+ if (array == null) {
+ return 0;
+ }
+ return Array.getLength(array);
+ }
+
+ /**
+ * <p>Checks whether two arrays are the same type taking into account
+ * multi-dimensional arrays.</p>
+ *
+ * @param array1 the first array, must not be {@code null}
+ * @param array2 the second array, must not be {@code null}
+ * @return {@code true} if type of arrays matches
+ * @throws IllegalArgumentException if either array is {@code null}
+ */
+ public static boolean isSameType(Object array1, Object array2) {
+ if (array1 == null || array2 == null) {
+ throw new IllegalArgumentException("The Array must not be null");
+ }
+ return array1.getClass().getName().equals(array2.getClass().getName());
+ }
+
+ // Reverse
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>There is no special handling for multi-dimensional arrays.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(Object[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ Object tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(long[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ long tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(int[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ int tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(short[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ short tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(char[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ char tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(byte[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ byte tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(double[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ double tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(float[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ float tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * <p>Reverses the order of the given array.</p>
+ *
+ * <p>This method does nothing for a {@code null} input array.</p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(boolean[] array) {
+ if (array == null) {
+ return;
+ }
+ int i = 0;
+ int j = array.length - 1;
+ boolean tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ // IndexOf search
+ // ----------------------------------------------------------------------
+
+ // Object IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given object in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @return the index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(Object[] array, Object objectToFind) {
+ return indexOf(array, objectToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given object in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @param startIndex the index to start searching at
+ * @return the index of the object within the array starting at the index,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ if (objectToFind == null) {
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else if (array.getClass().getComponentType().isInstance(objectToFind)) {
+ for (int i = startIndex; i < array.length; i++) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given object within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @return the last index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(Object[] array, Object objectToFind) {
+ return lastIndexOf(array, objectToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given object in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than
+ * the array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(Object[] array, Object objectToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ if (objectToFind == null) {
+ for (int i = startIndex; i >= 0; i--) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else if (array.getClass().getComponentType().isInstance(objectToFind)) {
+ for (int i = startIndex; i >= 0; i--) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the object is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param objectToFind the object to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(Object[] array, Object objectToFind) {
+ return indexOf(array, objectToFind) != INDEX_NOT_FOUND;
+ }
+
+ // long IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(long[] array, long valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(long[] array, long valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(long[] array, long valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(long[] array, long valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(long[] array, long valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // int IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(int[] array, int valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(int[] array, int valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(int[] array, int valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(int[] array, int valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(int[] array, int valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // short IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(short[] array, short valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(short[] array, short valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(short[] array, short valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(short[] array, short valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(short[] array, short valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // char IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int indexOf(char[] array, char valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int indexOf(char[] array, char valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int lastIndexOf(char[] array, char valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int lastIndexOf(char[] array, char valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ * @since 2.1
+ */
+ public static boolean contains(char[] array, char valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // byte IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(byte[] array, byte valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(byte[] array, byte valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(byte[] array, byte valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(byte[] array, byte valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(byte[] array, byte valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // double IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(double[] array, double valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value within a given tolerance in the array.
+ * This method will return the index of the first value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(double[] array, double valueToFind, double tolerance) {
+ return indexOf(array, valueToFind, 0, tolerance);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(double[] array, double valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.
+ * This method will return the index of the first value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(double[] array, double valueToFind, int startIndex, double tolerance) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ double min = valueToFind - tolerance;
+ double max = valueToFind + tolerance;
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] >= min && array[i] <= max) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(double[] array, double valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value within a given tolerance in the array.
+ * This method will return the index of the last value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(double[] array, double valueToFind, double tolerance) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE, tolerance);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(double[] array, double valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.
+ * This method will return the index of the last value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @param tolerance search for value within plus/minus this amount
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(double[] array, double valueToFind, int startIndex, double tolerance) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ double min = valueToFind - tolerance;
+ double max = valueToFind + tolerance;
+ for (int i = startIndex; i >= 0; i--) {
+ if (array[i] >= min && array[i] <= max) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(double[] array, double valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if a value falling within the given tolerance is in the
+ * given array. If the array contains a value within the inclusive range
+ * defined by (value - tolerance) to (value + tolerance).</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array
+ * is passed in.</p>
+ *
+ * @param array the array to search
+ * @param valueToFind the value to find
+ * @param tolerance the array contains the tolerance of the search
+ * @return true if value falling within tolerance is in array
+ */
+ public static boolean contains(double[] array, double valueToFind, double tolerance) {
+ return indexOf(array, valueToFind, 0, tolerance) != INDEX_NOT_FOUND;
+ }
+
+ // float IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(float[] array, float valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(float[] array, float valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(float[] array, float valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(float[] array, float valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(float[] array, float valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // boolean IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the index of the given value in the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(boolean[] array, boolean valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * <p>Finds the index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null}
+ * array input
+ */
+ public static int indexOf(boolean[] array, boolean valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Finds the last index of the given value within the array.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) if
+ * {@code null} array input.</p>
+ *
+ * @param array the array to travers backwords looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(boolean[] array, boolean valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * <p>Finds the last index of the given value in the array starting at the given index.</p>
+ *
+ * <p>This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than
+ * the array length will search from the end of the array.</p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to travers backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(boolean[] array, boolean valueToFind, int startIndex) {
+ if (ArrayUtils.isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ } else if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the value is in the given array.</p>
+ *
+ * <p>The method returns {@code false} if a {@code null} array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(boolean[] array, boolean valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ // Primitive/Object array converters
+ // ----------------------------------------------------------------------
+
+ // Character array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Characters to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Character} array, may be {@code null}
+ * @return a {@code char} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static char[] toPrimitive(Character[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ final char[] result = new char[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].charValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Character to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Character} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code char} array, {@code null} if null array input
+ */
+ public static char[] toPrimitive(Character[] array, char valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ final char[] result = new char[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Character b = array[i];
+ result[i] = (b == null ? valueForNull : b.charValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive chars to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code char} array
+ * @return a {@code Character} array, {@code null} if null array input
+ */
+ public static Character[] toObject(char[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_CHARACTER_OBJECT_ARRAY;
+ }
+ final Character[] result = new Character[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Character.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Long array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Longs to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Long} array, may be {@code null}
+ * @return a {@code long} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static long[] toPrimitive(Long[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+ final long[] result = new long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].longValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Long to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Long} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code long} array, {@code null} if null array input
+ */
+ public static long[] toPrimitive(Long[] array, long valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+ final long[] result = new long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Long b = array[i];
+ result[i] = (b == null ? valueForNull : b.longValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive longs to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code long} array
+ * @return a {@code Long} array, {@code null} if null array input
+ */
+ public static Long[] toObject(long[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_LONG_OBJECT_ARRAY;
+ }
+ final Long[] result = new Long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Long.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Int array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Integers to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Integer} array, may be {@code null}
+ * @return an {@code int} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static int[] toPrimitive(Integer[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ final int[] result = new int[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].intValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Integer to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Integer} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return an {@code int} array, {@code null} if null array input
+ */
+ public static int[] toPrimitive(Integer[] array, int valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ final int[] result = new int[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Integer b = array[i];
+ result[i] = (b == null ? valueForNull : b.intValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive ints to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array an {@code int} array
+ * @return an {@code Integer} array, {@code null} if null array input
+ */
+ public static Integer[] toObject(int[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_INTEGER_OBJECT_ARRAY;
+ }
+ final Integer[] result = new Integer[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Integer.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Short array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Shorts to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Short} array, may be {@code null}
+ * @return a {@code byte} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static short[] toPrimitive(Short[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+ final short[] result = new short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].shortValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Short to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Short} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code byte} array, {@code null} if null array input
+ */
+ public static short[] toPrimitive(Short[] array, short valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+ final short[] result = new short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Short b = array[i];
+ result[i] = (b == null ? valueForNull : b.shortValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive shorts to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code short} array
+ * @return a {@code Short} array, {@code null} if null array input
+ */
+ public static Short[] toObject(short[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_SHORT_OBJECT_ARRAY;
+ }
+ final Short[] result = new Short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Short.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Byte array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Bytes to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Byte} array, may be {@code null}
+ * @return a {@code byte} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static byte[] toPrimitive(Byte[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ final byte[] result = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].byteValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Bytes to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Byte} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code byte} array, {@code null} if null array input
+ */
+ public static byte[] toPrimitive(Byte[] array, byte valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ final byte[] result = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Byte b = array[i];
+ result[i] = (b == null ? valueForNull : b.byteValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive bytes to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code byte} array
+ * @return a {@code Byte} array, {@code null} if null array input
+ */
+ public static Byte[] toObject(byte[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BYTE_OBJECT_ARRAY;
+ }
+ final Byte[] result = new Byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Byte.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Double array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Doubles to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Double} array, may be {@code null}
+ * @return a {@code double} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static double[] toPrimitive(Double[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ final double[] result = new double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].doubleValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Doubles to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Double} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code double} array, {@code null} if null array input
+ */
+ public static double[] toPrimitive(Double[] array, double valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ final double[] result = new double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Double b = array[i];
+ result[i] = (b == null ? valueForNull : b.doubleValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive doubles to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code double} array
+ * @return a {@code Double} array, {@code null} if null array input
+ */
+ public static Double[] toObject(double[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_DOUBLE_OBJECT_ARRAY;
+ }
+ final Double[] result = new Double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Double.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Float array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Floats to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Float} array, may be {@code null}
+ * @return a {@code float} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static float[] toPrimitive(Float[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+ final float[] result = new float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].floatValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Floats to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Float} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code float} array, {@code null} if null array input
+ */
+ public static float[] toPrimitive(Float[] array, float valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+ final float[] result = new float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Float b = array[i];
+ result[i] = (b == null ? valueForNull : b.floatValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive floats to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code float} array
+ * @return a {@code Float} array, {@code null} if null array input
+ */
+ public static Float[] toObject(float[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_FLOAT_OBJECT_ARRAY;
+ }
+ final Float[] result = new Float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = Float.valueOf(array[i]);
+ }
+ return result;
+ }
+
+ // Boolean array converters
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Converts an array of object Booleans to primitives.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Boolean} array, may be {@code null}
+ * @return a {@code boolean} array, {@code null} if null array input
+ * @throws NullPointerException if array content is {@code null}
+ */
+ public static boolean[] toPrimitive(Boolean[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+ final boolean[] result = new boolean[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].booleanValue();
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of object Booleans to primitives handling {@code null}.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code Boolean} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code boolean} array, {@code null} if null array input
+ */
+ public static boolean[] toPrimitive(Boolean[] array, boolean valueForNull) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+ final boolean[] result = new boolean[array.length];
+ for (int i = 0; i < array.length; i++) {
+ Boolean b = array[i];
+ result[i] = (b == null ? valueForNull : b.booleanValue());
+ }
+ return result;
+ }
+
+ /**
+ * <p>Converts an array of primitive booleans to objects.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code boolean} array
+ * @return a {@code Boolean} array, {@code null} if null array input
+ */
+ public static Boolean[] toObject(boolean[] array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return EMPTY_BOOLEAN_OBJECT_ARRAY;
+ }
+ final Boolean[] result = new Boolean[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ return result;
+ }
+
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Checks if an array of Objects is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(Object[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive longs is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(long[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive ints is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive shorts is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(short[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive chars is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(char[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive bytes is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive doubles is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(double[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive floats is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(float[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * <p>Checks if an array of primitive booleans is empty or {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(boolean[] array) {
+ return array == null || array.length == 0;
+ }
+
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Checks if an array of Objects is not empty or not {@code null}.</p>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static <T> boolean isNotEmpty(T[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive longs is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(long[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive ints is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(int[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive shorts is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(short[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive chars is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(char[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive bytes is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(byte[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive doubles is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(double[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive floats is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(float[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Checks if an array of primitive booleans is not empty or not {@code null}.</p>
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty or not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(boolean[] array) {
+ return (array != null && array.length != 0);
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(null, null) = null
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * ArrayUtils.addAll([null], [null]) = [null, null]
+ * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array1 the first array whose elements are added to the new array, may be {@code null}
+ * @param array2 the second array whose elements are added to the new array, may be {@code null}
+ * @return The new array, {@code null} if both arrays are {@code null}.
+ * The type of the new array is the type of the first array,
+ * unless the first array is null, in which case the type is the same as the second array.
+ * @since 2.1
+ * @throws IllegalArgumentException if the array types are incompatible
+ */
+ public static <T> T[] addAll(T[] array1, T... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ final Class<?> type1 = array1.getClass().getComponentType();
+ @SuppressWarnings("unchecked") // OK, because array is of type T
+ T[] joinedArray = (T[]) Array.newInstance(type1, array1.length + array2.length);
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ try {
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ } catch (ArrayStoreException ase) {
+ // Check if problem was due to incompatible types
+ /*
+ * We do this here, rather than before the copy because:
+ * - it would be a wasted check most of the time
+ * - safer, in case check turns out to be too strict
+ */
+ final Class<?> type2 = array2.getClass().getComponentType();
+ if (!type1.isAssignableFrom(type2)){
+ throw new IllegalArgumentException("Cannot store "+type2.getName()+" in an array of "
+ +type1.getName(), ase);
+ }
+ throw ase; // No, so rethrow original
+ }
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new boolean[] array.
+ * @since 2.1
+ */
+ public static boolean[] addAll(boolean[] array1, boolean... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ boolean[] joinedArray = new boolean[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new char[] array.
+ * @since 2.1
+ */
+ public static char[] addAll(char[] array1, char... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ char[] joinedArray = new char[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new byte[] array.
+ * @since 2.1
+ */
+ public static byte[] addAll(byte[] array1, byte... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ byte[] joinedArray = new byte[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new short[] array.
+ * @since 2.1
+ */
+ public static short[] addAll(short[] array1, short... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ short[] joinedArray = new short[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new int[] array.
+ * @since 2.1
+ */
+ public static int[] addAll(int[] array1, int... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ int[] joinedArray = new int[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new long[] array.
+ * @since 2.1
+ */
+ public static long[] addAll(long[] array1, long... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ long[] joinedArray = new long[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new float[] array.
+ * @since 2.1
+ */
+ public static float[] addAll(float[] array1, float... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ float[] joinedArray = new float[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Adds all the elements of the given arrays into a new array.</p>
+ * <p>The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new double[] array.
+ * @since 2.1
+ */
+ public static double[] addAll(double[] array1, double... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ } else if (array2 == null) {
+ return clone(array1);
+ }
+ double[] joinedArray = new double[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element, unless the element itself is null,
+ * in which case the return type is Object[]</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, null) = [null]
+ * ArrayUtils.add(null, "a") = ["a"]
+ * ArrayUtils.add(["a"], null) = ["a", null]
+ * ArrayUtils.add(["a"], "b") = ["a", "b"]
+ * ArrayUtils.add(["a", "b"], "c") = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to "add" the element to, may be {@code null}
+ * @param element the object to add, may be {@code null}
+ * @return A new array containing the existing elements plus the new element
+ * The returned array type will be that of the input array (unless null),
+ * in which case it will have the same type as the element.
+ * If both are null, an IllegalArgumentException is thrown
+ * @since 2.1
+ * @throws IllegalArgumentException if both arguments are null
+ */
+ public static <T> T[] add(T[] array, T element) {
+ Class<?> type;
+ if (array != null){
+ type = array.getClass();
+ } else if (element != null) {
+ type = element.getClass();
+ } else {
+ throw new IllegalArgumentException("Arguments cannot both be null");
+ }
+ @SuppressWarnings("unchecked") // type must be T
+ T[] newArray = (T[]) copyArrayGrow1(array, type);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, true) = [true]
+ * ArrayUtils.add([true], false) = [true, false]
+ * ArrayUtils.add([true, false], true) = [true, false, true]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static boolean[] add(boolean[] array, boolean element) {
+ boolean[] newArray = (boolean[])copyArrayGrow1(array, Boolean.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static byte[] add(byte[] array, byte element) {
+ byte[] newArray = (byte[])copyArrayGrow1(array, Byte.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, '0') = ['0']
+ * ArrayUtils.add(['1'], '0') = ['1', '0']
+ * ArrayUtils.add(['1', '0'], '1') = ['1', '0', '1']
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static char[] add(char[] array, char element) {
+ char[] newArray = (char[])copyArrayGrow1(array, Character.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static double[] add(double[] array, double element) {
+ double[] newArray = (double[])copyArrayGrow1(array, Double.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static float[] add(float[] array, float element) {
+ float[] newArray = (float[])copyArrayGrow1(array, Float.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static int[] add(int[] array, int element) {
+ int[] newArray = (int[])copyArrayGrow1(array, Integer.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static long[] add(long[] array, long element) {
+ long[] newArray = (long[])copyArrayGrow1(array, Long.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * <p>Copies the given array and adds the given element at the end of the new array.</p>
+ *
+ * <p>The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static short[] add(short[] array, short element) {
+ short[] newArray = (short[])copyArrayGrow1(array, Short.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Returns a copy of the given array of size 1 greater than the argument.
+ * The last value of the array is left to the default value.
+ *
+ * @param array The array to copy, must not be {@code null}.
+ * @param newArrayComponentType If {@code array} is {@code null}, create a
+ * size 1 array of this type.
+ * @return A new copy of the array of size 1 greater than the input.
+ */
+ private static Object copyArrayGrow1(Object array, Class<?> newArrayComponentType) {
+ if (array != null) {
+ int arrayLength = Array.getLength(array);
+ Object newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1);
+ System.arraycopy(array, 0, newArray, 0, arrayLength);
+ return newArray;
+ }
+ return Array.newInstance(newArrayComponentType, 1);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0, null) = [null]
+ * ArrayUtils.add(null, 0, "a") = ["a"]
+ * ArrayUtils.add(["a"], 1, null) = ["a", null]
+ * ArrayUtils.add(["a"], 1, "b") = ["a", "b"]
+ * ArrayUtils.add(["a", "b"], 3, "c") = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ * @throws IllegalArgumentException if both array and element are null
+ */
+ public static <T> T[] add(T[] array, int index, T element) {
+ Class<?> clss = null;
+ if (array != null) {
+ clss = array.getClass().getComponentType();
+ } else if (element != null) {
+ clss = element.getClass();
+ } else {
+ throw new IllegalArgumentException("Array and element cannot both be null");
+ }
+ @SuppressWarnings("unchecked") // the add method creates an array of type clss, which is type T
+ final T[] newArray = (T[]) add(array, index, element, clss);
+ return newArray;
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0, true) = [true]
+ * ArrayUtils.add([true], 0, false) = [false, true]
+ * ArrayUtils.add([false], 1, true) = [false, true]
+ * ArrayUtils.add([true, false], 1, true) = [true, true, false]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static boolean[] add(boolean[] array, int index, boolean element) {
+ return (boolean[]) add(array, index, Boolean.valueOf(element), Boolean.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add(null, 0, 'a') = ['a']
+ * ArrayUtils.add(['a'], 0, 'b') = ['b', 'a']
+ * ArrayUtils.add(['a', 'b'], 0, 'c') = ['c', 'a', 'b']
+ * ArrayUtils.add(['a', 'b'], 1, 'k') = ['a', 'k', 'b']
+ * ArrayUtils.add(['a', 'b', 'c'], 1, 't') = ['a', 't', 'b', 'c']
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static char[] add(char[] array, int index, char element) {
+ return (char[]) add(array, index, Character.valueOf(element), Character.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 3) = [2, 6, 3]
+ * ArrayUtils.add([2, 6], 0, 1) = [1, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static byte[] add(byte[] array, int index, byte element) {
+ return (byte[]) add(array, index, Byte.valueOf(element), Byte.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 10) = [2, 6, 10]
+ * ArrayUtils.add([2, 6], 0, -4) = [-4, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static short[] add(short[] array, int index, short element) {
+ return (short[]) add(array, index, Short.valueOf(element), Short.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 10) = [2, 6, 10]
+ * ArrayUtils.add([2, 6], 0, -4) = [-4, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static int[] add(int[] array, int index, int element) {
+ return (int[]) add(array, index, Integer.valueOf(element), Integer.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1L], 0, 2L) = [2L, 1L]
+ * ArrayUtils.add([2L, 6L], 2, 10L) = [2L, 6L, 10L]
+ * ArrayUtils.add([2L, 6L], 0, -4L) = [-4L, 2L, 6L]
+ * ArrayUtils.add([2L, 6L, 3L], 2, 1L) = [2L, 6L, 1L, 3L]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static long[] add(long[] array, int index, long element) {
+ return (long[]) add(array, index, Long.valueOf(element), Long.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1.1f], 0, 2.2f) = [2.2f, 1.1f]
+ * ArrayUtils.add([2.3f, 6.4f], 2, 10.5f) = [2.3f, 6.4f, 10.5f]
+ * ArrayUtils.add([2.6f, 6.7f], 0, -4.8f) = [-4.8f, 2.6f, 6.7f]
+ * ArrayUtils.add([2.9f, 6.0f, 0.3f], 2, 1.0f) = [2.9f, 6.0f, 1.0f, 0.3f]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static float[] add(float[] array, int index, float element) {
+ return (float[]) add(array, index, Float.valueOf(element), Float.TYPE);
+ }
+
+ /**
+ * <p>Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.</p>
+ *
+ * <pre>
+ * ArrayUtils.add([1.1], 0, 2.2) = [2.2, 1.1]
+ * ArrayUtils.add([2.3, 6.4], 2, 10.5) = [2.3, 6.4, 10.5]
+ * ArrayUtils.add([2.6, 6.7], 0, -4.8) = [-4.8, 2.6, 6.7]
+ * ArrayUtils.add([2.9, 6.0, 0.3], 2, 1.0) = [2.9, 6.0, 1.0, 0.3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index > array.length).
+ */
+ public static double[] add(double[] array, int index, double element) {
+ return (double[]) add(array, index, Double.valueOf(element), Double.TYPE);
+ }
+
+ /**
+ * Underlying implementation of add(array, index, element) methods.
+ * The last parameter is the class, which may not equal element.getClass
+ * for primitives.
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @param clss the type of the element being added
+ * @return A new array containing the existing elements and the new element
+ */
+ private static Object add(Object array, int index, Object element, Class<?> clss) {
+ if (array == null) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: 0");
+ }
+ Object joinedArray = Array.newInstance(clss, 1);
+ Array.set(joinedArray, 0, element);
+ return joinedArray;
+ }
+ int length = Array.getLength(array);
+ if (index > length || index < 0) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+ Object result = Array.newInstance(clss, length + 1);
+ System.arraycopy(array, 0, result, 0, index);
+ Array.set(result, index, element);
+ if (index < length) {
+ System.arraycopy(array, index, result, index + 1, length - index);
+ }
+ return result;
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove(["a"], 0) = []
+ * ArrayUtils.remove(["a", "b"], 0) = ["b"]
+ * ArrayUtils.remove(["a", "b"], 1) = ["a"]
+ * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ @SuppressWarnings("unchecked") // remove() always creates an array of the same type as its input
+ public static <T> T[] remove(T[] array, int index) {
+ return (T[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, "a") = null
+ * ArrayUtils.removeElement([], "a") = []
+ * ArrayUtils.removeElement(["a"], "b") = ["a"]
+ * ArrayUtils.removeElement(["a", "b"], "a") = ["b"]
+ * ArrayUtils.removeElement(["a", "b", "a"], "a") = ["b", "a"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static <T> T[] removeElement(T[] array, Object element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([true], 0) = []
+ * ArrayUtils.remove([true, false], 0) = [false]
+ * ArrayUtils.remove([true, false], 1) = [true]
+ * ArrayUtils.remove([true, true, false], 1) = [true, false]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static boolean[] remove(boolean[] array, int index) {
+ return (boolean[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, true) = null
+ * ArrayUtils.removeElement([], true) = []
+ * ArrayUtils.removeElement([true], false) = [true]
+ * ArrayUtils.removeElement([true, false], false) = [true]
+ * ArrayUtils.removeElement([true, false, true], true) = [false, true]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static boolean[] removeElement(boolean[] array, boolean element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([1, 0], 0) = [0]
+ * ArrayUtils.remove([1, 0], 1) = [1]
+ * ArrayUtils.remove([1, 0, 1], 1) = [1, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static byte[] remove(byte[] array, int index) {
+ return (byte[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 0) = [1]
+ * ArrayUtils.removeElement([1, 0], 0) = [1]
+ * ArrayUtils.removeElement([1, 0, 1], 1) = [0, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static byte[] removeElement(byte[] array, byte element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove(['a'], 0) = []
+ * ArrayUtils.remove(['a', 'b'], 0) = ['b']
+ * ArrayUtils.remove(['a', 'b'], 1) = ['a']
+ * ArrayUtils.remove(['a', 'b', 'c'], 1) = ['a', 'c']
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static char[] remove(char[] array, int index) {
+ return (char[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 'a') = null
+ * ArrayUtils.removeElement([], 'a') = []
+ * ArrayUtils.removeElement(['a'], 'b') = ['a']
+ * ArrayUtils.removeElement(['a', 'b'], 'a') = ['b']
+ * ArrayUtils.removeElement(['a', 'b', 'a'], 'a') = ['b', 'a']
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static char[] removeElement(char[] array, char element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1.1], 0) = []
+ * ArrayUtils.remove([2.5, 6.0], 0) = [6.0]
+ * ArrayUtils.remove([2.5, 6.0], 1) = [2.5]
+ * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static double[] remove(double[] array, int index) {
+ return (double[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1.1) = null
+ * ArrayUtils.removeElement([], 1.1) = []
+ * ArrayUtils.removeElement([1.1], 1.2) = [1.1]
+ * ArrayUtils.removeElement([1.1, 2.3], 1.1) = [2.3]
+ * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static double[] removeElement(double[] array, double element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1.1], 0) = []
+ * ArrayUtils.remove([2.5, 6.0], 0) = [6.0]
+ * ArrayUtils.remove([2.5, 6.0], 1) = [2.5]
+ * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static float[] remove(float[] array, int index) {
+ return (float[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1.1) = null
+ * ArrayUtils.removeElement([], 1.1) = []
+ * ArrayUtils.removeElement([1.1], 1.2) = [1.1]
+ * ArrayUtils.removeElement([1.1, 2.3], 1.1) = [2.3]
+ * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static float[] removeElement(float[] array, float element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static int[] remove(int[] array, int index) {
+ return (int[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static int[] removeElement(int[] array, int element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static long[] remove(long[] array, int index) {
+ return (long[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static long[] removeElement(long[] array, long element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static short[] remove(short[] array, int index) {
+ return (short[]) remove((Object) array, index);
+ }
+
+ /**
+ * <p>Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contains
+ * such an element, no elements are removed from the array.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static short[] removeElement(short[] array, short element) {
+ int index = indexOf(array, element);
+ if (index == INDEX_NOT_FOUND) {
+ return clone(array);
+ }
+ return remove(array, index);
+ }
+
+ /**
+ * <p>Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ private static Object remove(Object array, int index) {
+ int length = getLength(array);
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+
+ Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);
+ System.arraycopy(array, 0, result, 0, index);
+ if (index < length - 1) {
+ System.arraycopy(array, index + 1, result, index, length - index - 1);
+ }
+
+ return result;
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll(["a", "b", "c"], 0, 2) = ["b"]
+ * ArrayUtils.removeAll(["a", "b", "c"], 1, 2) = ["a"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ @SuppressWarnings("unchecked")
+ // removeAll() always creates an array of the same type as its input
+ public static <T> T[] removeAll(T[] array, int... indices) {
+ return (T[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, "a", "b") = null
+ * ArrayUtils.removeElements([], "a", "b") = []
+ * ArrayUtils.removeElements(["a"], "b", "c") = ["a"]
+ * ArrayUtils.removeElements(["a", "b"], "a", "c") = ["b"]
+ * ArrayUtils.removeElements(["a", "b", "a"], "a") = ["b", "a"]
+ * ArrayUtils.removeElements(["a", "b", "a"], "a", "a") = ["b"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static <T> T[] removeElements(T[] array, T... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<T, MutableInt> occurrences = new HashMap<T, MutableInt>(values.length);
+ for (T v : values) {
+ MutableInt count = occurrences.get(v);
+ if (count == null) {
+ occurrences.put(v, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
+ T v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v, found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static byte[] removeAll(byte[] array, int... indices) {
+ return (byte[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static byte[] removeElements(byte[] array, byte... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Byte, MutableInt> occurrences = new HashMap<Byte, MutableInt>(values.length);
+ for (byte v : values) {
+ Byte boxed = Byte.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Byte, MutableInt> e : occurrences.entrySet()) {
+ Byte v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.byteValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static short[] removeAll(short[] array, int... indices) {
+ return (short[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static short[] removeElements(short[] array, short... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Short, MutableInt> occurrences = new HashMap<Short, MutableInt>(values.length);
+ for (short v : values) {
+ Short boxed = Short.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Short, MutableInt> e : occurrences.entrySet()) {
+ Short v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.shortValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static int[] removeAll(int[] array, int... indices) {
+ return (int[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static int[] removeElements(int[] array, int... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Integer, MutableInt> occurrences = new HashMap<Integer, MutableInt>(values.length);
+ for (int v : values) {
+ Integer boxed = Integer.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Integer, MutableInt> e : occurrences.entrySet()) {
+ Integer v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.intValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static char[] removeAll(char[] array, int... indices) {
+ return (char[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static char[] removeElements(char[] array, char... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Character, MutableInt> occurrences = new HashMap<Character, MutableInt>(values.length);
+ for (char v : values) {
+ Character boxed = Character.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Character, MutableInt> e : occurrences.entrySet()) {
+ Character v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.charValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static long[] removeAll(long[] array, int... indices) {
+ return (long[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static long[] removeElements(long[] array, long... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Long, MutableInt> occurrences = new HashMap<Long, MutableInt>(values.length);
+ for (long v : values) {
+ Long boxed = Long.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Long, MutableInt> e : occurrences.entrySet()) {
+ Long v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.longValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static float[] removeAll(float[] array, int... indices) {
+ return (float[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static float[] removeElements(float[] array, float... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Float, MutableInt> occurrences = new HashMap<Float, MutableInt>(values.length);
+ for (float v : values) {
+ Float boxed = Float.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Float, MutableInt> e : occurrences.entrySet()) {
+ Float v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.floatValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static double[] removeAll(double[] array, int... indices) {
+ return (double[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static double[] removeElements(double[] array, double... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Double, MutableInt> occurrences = new HashMap<Double, MutableInt>(values.length);
+ for (double v : values) {
+ Double boxed = Double.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Double, MutableInt> e : occurrences.entrySet()) {
+ Double v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.doubleValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * <p>Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.</p>
+ *
+ * <p>If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeAll([true, false, true], 0, 2) = [false]
+ * ArrayUtils.removeAll([true, false, true], 1, 2) = [true]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index < 0 || index >= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static boolean[] removeAll(boolean[] array, int... indices) {
+ return (boolean[]) removeAll((Object) array, clone(indices));
+ }
+
+ /**
+ * <p>Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.</p>
+ *
+ * <p>This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.</p>
+ *
+ * <pre>
+ * ArrayUtils.removeElements(null, true, false) = null
+ * ArrayUtils.removeElements([], true, false) = []
+ * ArrayUtils.removeElements([true], false, false) = [true]
+ * ArrayUtils.removeElements([true, false], true, true) = [false]
+ * ArrayUtils.removeElements([true, false, true], true) = [false, true]
+ * ArrayUtils.removeElements([true, false, true], true, true) = [false]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static boolean[] removeElements(boolean[] array, boolean... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ HashMap<Boolean, MutableInt> occurrences = new HashMap<Boolean, MutableInt>(values.length);
+ for (boolean v : values) {
+ Boolean boxed = Boolean.valueOf(v);
+ MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ HashSet<Integer> toRemove = new HashSet<Integer>();
+ for (Map.Entry<Boolean, MutableInt> e : occurrences.entrySet()) {
+ Boolean v = e.getKey();
+ int found = 0;
+ for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
+ found = indexOf(array, v.booleanValue(), found);
+ if (found < 0) {
+ break;
+ }
+ toRemove.add(found++);
+ }
+ }
+ return removeAll(array, extractIndices(toRemove));
+ }
+
+ /**
+ * Removes multiple array elements specified by index.
+ * @param array source
+ * @param indices to remove, WILL BE SORTED--so only clones of user-owned arrays!
+ * @return new array of same type minus elements specified by unique values of {@code indices}
+ * @since 3.0.1
+ */
+ private static Object removeAll(Object array, int... indices) {
+ int length = getLength(array);
+ int diff = 0;
+
+ if (isNotEmpty(indices)) {
+ Arrays.sort(indices);
+
+ int i = indices.length;
+ int prevIndex = length;
+ while (--i >= 0) {
+ int index = indices[i];
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+ if (index >= prevIndex) {
+ continue;
+ }
+ diff++;
+ prevIndex = index;
+ }
+ }
+ Object result = Array.newInstance(array.getClass().getComponentType(), length - diff);
+ if (diff < length) {
+ int end = length;
+ int dest = length - diff;
+ for (int i = indices.length - 1; i >= 0; i--) {
+ int index = indices[i];
+ if (end - index > 1) {
+ int cp = end - index - 1;
+ dest -= cp;
+ System.arraycopy(array, index + 1, result, dest, cp);
+ }
+ end = index;
+ }
+ if (end > 0) {
+ System.arraycopy(array, 0, result, 0, end);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Extract a set of Integer indices into an int[].
+ * @param coll {@code HashSet} of {@code Integer}
+ * @return int[]
+ * @since 3.0.1
+ */
+ private static int[] extractIndices(HashSet<Integer> coll) {
+ int[] result = new int[coll.size()];
+ int i = 0;
+ for (Integer index : coll) {
+ result[i++] = index.intValue();
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/org/apache/commons/lang3/CharSequenceUtils.java b/src/org/apache/commons/lang3/CharSequenceUtils.java
new file mode 100644
index 0000000..e459699
--- /dev/null
+++ b/src/org/apache/commons/lang3/CharSequenceUtils.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+/**
+ * <p>Operations on {@link java.lang.CharSequence} that are
+ * {@code null} safe.</p>
+ *
+ * @see java.lang.CharSequence
+ * @since 3.0
+ * @version $Id$
+ */
+public class CharSequenceUtils {
+
+ /**
+ * <p>{@code CharSequenceUtils} instances should NOT be constructed in
+ * standard programming. </p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public CharSequenceUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns a new {@code CharSequence} that is a subsequence of this
+ * sequence starting with the {@code char} value at the specified index.</p>
+ *
+ * <p>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.</p>
+ *
+ * @param cs the specified subsequence, null returns null
+ * @param start the start index, inclusive, valid
+ * @return a new subsequence, may be null
+ * @throws IndexOutOfBoundsException if {@code start} is negative or if
+ * {@code start} is greater than {@code length()}
+ */
+ public static CharSequence subSequence(CharSequence cs, int start) {
+ return cs == null ? null : cs.subSequence(start, cs.length());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the first index in the {@code CharSequence} that matches the
+ * specified character.</p>
+ *
+ * @param cs the {@code CharSequence} to be processed, not null
+ * @param searchChar the char to be searched for
+ * @param start the start index, negative starts at the string start
+ * @return the index where the search char was found, -1 if not found
+ */
+ static int indexOf(CharSequence cs, int searchChar, int start) {
+ if (cs instanceof String) {
+ return ((String) cs).indexOf(searchChar, start);
+ } else {
+ int sz = cs.length();
+ if (start < 0) {
+ start = 0;
+ }
+ for (int i = start; i < sz; i++) {
+ if (cs.charAt(i) == searchChar) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ }
+
+ /**
+ * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @param searchChar the {@code CharSequence} to be searched for
+ * @param start the start index
+ * @return the index where the search sequence was found
+ */
+ static int indexOf(CharSequence cs, CharSequence searchChar, int start) {
+ return cs.toString().indexOf(searchChar.toString(), start);
+// if (cs instanceof String && searchChar instanceof String) {
+// // TODO: Do we assume searchChar is usually relatively small;
+// // If so then calling toString() on it is better than reverting to
+// // the green implementation in the else block
+// return ((String) cs).indexOf((String) searchChar, start);
+// } else {
+// // TODO: Implement rather than convert to String
+// return cs.toString().indexOf(searchChar.toString(), start);
+// }
+ }
+
+ /**
+ * <p>Finds the last index in the {@code CharSequence} that matches the
+ * specified character.</p>
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @param searchChar the char to be searched for
+ * @param start the start index, negative returns -1, beyond length starts at end
+ * @return the index where the search char was found, -1 if not found
+ */
+ static int lastIndexOf(CharSequence cs, int searchChar, int start) {
+ if (cs instanceof String) {
+ return ((String) cs).lastIndexOf(searchChar, start);
+ } else {
+ int sz = cs.length();
+ if (start < 0) {
+ return -1;
+ }
+ if (start >= sz) {
+ start = sz - 1;
+ }
+ for (int i = start; i >= 0; --i) {
+ if (cs.charAt(i) == searchChar) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ }
+
+ /**
+ * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @param searchChar the {@code CharSequence} to be searched for
+ * @param start the start index
+ * @return the index where the search sequence was found
+ */
+ static int lastIndexOf(CharSequence cs, CharSequence searchChar, int start) {
+ return cs.toString().lastIndexOf(searchChar.toString(), start);
+// if (cs instanceof String && searchChar instanceof String) {
+// // TODO: Do we assume searchChar is usually relatively small;
+// // If so then calling toString() on it is better than reverting to
+// // the green implementation in the else block
+// return ((String) cs).lastIndexOf((String) searchChar, start);
+// } else {
+// // TODO: Implement rather than convert to String
+// return cs.toString().lastIndexOf(searchChar.toString(), start);
+// }
+ }
+
+ /**
+ * Green implementation of toCharArray.
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @return the resulting char array
+ */
+ static char[] toCharArray(CharSequence cs) {
+ if (cs instanceof String) {
+ return ((String) cs).toCharArray();
+ } else {
+ int sz = cs.length();
+ char[] array = new char[cs.length()];
+ for (int i = 0; i < sz; i++) {
+ array[i] = cs.charAt(i);
+ }
+ return array;
+ }
+ }
+
+ /**
+ * Green implementation of regionMatches.
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @param ignoreCase whether or not to be case insensitive
+ * @param thisStart the index to start on the {@code cs} CharSequence
+ * @param substring the {@code CharSequence} to be looked for
+ * @param start the index to start on the {@code substring} CharSequence
+ * @param length character length of the region
+ * @return whether the region matched
+ */
+ static boolean regionMatches(CharSequence cs, boolean ignoreCase, int thisStart,
+ CharSequence substring, int start, int length) {
+ if (cs instanceof String && substring instanceof String) {
+ return ((String) cs).regionMatches(ignoreCase, thisStart, ((String) substring), start, length);
+ } else {
+ // TODO: Implement rather than convert to String
+ return cs.toString().regionMatches(ignoreCase, thisStart, substring.toString(), start, length);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/org/apache/commons/lang3/CharUtils.java b/src/org/apache/commons/lang3/CharUtils.java
new file mode 100644
index 0000000..4a6f188
--- /dev/null
+++ b/src/org/apache/commons/lang3/CharUtils.java
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+/**
+ * <p>Operations on char primitives and Character objects.</p>
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behaviour in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 2.1
+ * @version $Id$
+ */
+public class CharUtils {
+
+ private static final String[] CHAR_STRING_ARRAY = new String[128];
+
+ /**
+ * {@code \u000a} linefeed LF ('\n').
+ *
+ * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 2.2
+ */
+ public static final char LF = '\n';
+
+ /**
+ * {@code \u000d} carriage return CR ('\r').
+ *
+ * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 2.2
+ */
+ public static final char CR = '\r';
+
+
+ static {
+ for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) {
+ CHAR_STRING_ARRAY[c] = String.valueOf(c);
+ }
+ }
+
+ /**
+ * <p>{@code CharUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code CharUtils.toString('c');}.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public CharUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the character to a Character.</p>
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same Character object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toCharacterObject(' ') = ' '
+ * CharUtils.toCharacterObject('A') = 'A'
+ * </pre>
+ *
+ * @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127.
+ * @param ch the character to convert
+ * @return a Character of the specified character
+ */
+ @Deprecated
+ public static Character toCharacterObject(char ch) {
+ return Character.valueOf(ch);
+ }
+
+ /**
+ * <p>Converts the String to a Character using the first character, returning
+ * null for empty Strings.</p>
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same Character object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toCharacterObject(null) = null
+ * CharUtils.toCharacterObject("") = null
+ * CharUtils.toCharacterObject("A") = 'A'
+ * CharUtils.toCharacterObject("BA") = 'B'
+ * </pre>
+ *
+ * @param str the character to convert
+ * @return the Character value of the first letter of the String
+ */
+ public static Character toCharacterObject(String str) {
+ if (StringUtils.isEmpty(str)) {
+ return null;
+ }
+ return Character.valueOf(str.charAt(0));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the Character to a char throwing an exception for {@code null}.</p>
+ *
+ * <pre>
+ * CharUtils.toChar(' ') = ' '
+ * CharUtils.toChar('A') = 'A'
+ * CharUtils.toChar(null) throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the char value of the Character
+ * @throws IllegalArgumentException if the Character is null
+ */
+ public static char toChar(Character ch) {
+ if (ch == null) {
+ throw new IllegalArgumentException("The Character must not be null");
+ }
+ return ch.charValue();
+ }
+
+ /**
+ * <p>Converts the Character to a char handling {@code null}.</p>
+ *
+ * <pre>
+ * CharUtils.toChar(null, 'X') = 'X'
+ * CharUtils.toChar(' ', 'X') = ' '
+ * CharUtils.toChar('A', 'X') = 'A'
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the value to use if the Character is null
+ * @return the char value of the Character or the default if null
+ */
+ public static char toChar(Character ch, char defaultValue) {
+ if (ch == null) {
+ return defaultValue;
+ }
+ return ch.charValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the String to a char using the first character, throwing
+ * an exception on empty Strings.</p>
+ *
+ * <pre>
+ * CharUtils.toChar("A") = 'A'
+ * CharUtils.toChar("BA") = 'B'
+ * CharUtils.toChar(null) throws IllegalArgumentException
+ * CharUtils.toChar("") throws IllegalArgumentException
+ * </pre>
+ *
+ * @param str the character to convert
+ * @return the char value of the first letter of the String
+ * @throws IllegalArgumentException if the String is empty
+ */
+ public static char toChar(String str) {
+ if (StringUtils.isEmpty(str)) {
+ throw new IllegalArgumentException("The String must not be empty");
+ }
+ return str.charAt(0);
+ }
+
+ /**
+ * <p>Converts the String to a char using the first character, defaulting
+ * the value on empty Strings.</p>
+ *
+ * <pre>
+ * CharUtils.toChar(null, 'X') = 'X'
+ * CharUtils.toChar("", 'X') = 'X'
+ * CharUtils.toChar("A", 'X') = 'A'
+ * CharUtils.toChar("BA", 'X') = 'B'
+ * </pre>
+ *
+ * @param str the character to convert
+ * @param defaultValue the value to use if the Character is null
+ * @return the char value of the first letter of the String or the default if null
+ */
+ public static char toChar(String str, char defaultValue) {
+ if (StringUtils.isEmpty(str)) {
+ return defaultValue;
+ }
+ return str.charAt(0);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.</p>
+ *
+ * <p>This method coverts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3') = 3
+ * CharUtils.toIntValue('A') throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the int value of the character
+ * @throws IllegalArgumentException if the character is not ASCII numeric
+ */
+ public static int toIntValue(char ch) {
+ if (isAsciiNumeric(ch) == false) {
+ throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'");
+ }
+ return ch - 48;
+ }
+
+ /**
+ * <p>Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.</p>
+ *
+ * <p>This method coverts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3', -1) = 3
+ * CharUtils.toIntValue('A', -1) = -1
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the default value to use if the character is not numeric
+ * @return the int value of the character
+ */
+ public static int toIntValue(char ch, int defaultValue) {
+ if (isAsciiNumeric(ch) == false) {
+ return defaultValue;
+ }
+ return ch - 48;
+ }
+
+ /**
+ * <p>Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.</p>
+ *
+ * <p>This method coverts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3') = 3
+ * CharUtils.toIntValue(null) throws IllegalArgumentException
+ * CharUtils.toIntValue('A') throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert, not null
+ * @return the int value of the character
+ * @throws IllegalArgumentException if the Character is not ASCII numeric or is null
+ */
+ public static int toIntValue(Character ch) {
+ if (ch == null) {
+ throw new IllegalArgumentException("The character must not be null");
+ }
+ return toIntValue(ch.charValue());
+ }
+
+ /**
+ * <p>Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.</p>
+ *
+ * <p>This method coverts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue(null, -1) = -1
+ * CharUtils.toIntValue('3', -1) = 3
+ * CharUtils.toIntValue('A', -1) = -1
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the default value to use if the character is not numeric
+ * @return the int value of the character
+ */
+ public static int toIntValue(Character ch, int defaultValue) {
+ if (ch == null) {
+ return defaultValue;
+ }
+ return toIntValue(ch.charValue(), defaultValue);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts the character to a String that contains the one character.</p>
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same String object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toString(' ') = " "
+ * CharUtils.toString('A') = "A"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return a String containing the one specified character
+ */
+ public static String toString(char ch) {
+ if (ch < 128) {
+ return CHAR_STRING_ARRAY[ch];
+ }
+ return new String(new char[] {ch});
+ }
+
+ /**
+ * <p>Converts the character to a String that contains the one character.</p>
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same String object each time.</p>
+ *
+ * <p>If {@code null} is passed in, {@code null} will be returned.</p>
+ *
+ * <pre>
+ * CharUtils.toString(null) = null
+ * CharUtils.toString(' ') = " "
+ * CharUtils.toString('A') = "A"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return a String containing the one specified character
+ */
+ public static String toString(Character ch) {
+ if (ch == null) {
+ return null;
+ }
+ return toString(ch.charValue());
+ }
+
+ //--------------------------------------------------------------------------
+ /**
+ * <p>Converts the string to the Unicode format '\u0020'.</p>
+ *
+ * <p>This format is the Java source code format.</p>
+ *
+ * <pre>
+ * CharUtils.unicodeEscaped(' ') = "\u0020"
+ * CharUtils.unicodeEscaped('A') = "\u0041"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the escaped Unicode string
+ */
+ public static String unicodeEscaped(char ch) {
+ if (ch < 0x10) {
+ return "\\u000" + Integer.toHexString(ch);
+ } else if (ch < 0x100) {
+ return "\\u00" + Integer.toHexString(ch);
+ } else if (ch < 0x1000) {
+ return "\\u0" + Integer.toHexString(ch);
+ }
+ return "\\u" + Integer.toHexString(ch);
+ }
+
+ /**
+ * <p>Converts the string to the Unicode format '\u0020'.</p>
+ *
+ * <p>This format is the Java source code format.</p>
+ *
+ * <p>If {@code null} is passed in, {@code null} will be returned.</p>
+ *
+ * <pre>
+ * CharUtils.unicodeEscaped(null) = null
+ * CharUtils.unicodeEscaped(' ') = "\u0020"
+ * CharUtils.unicodeEscaped('A') = "\u0041"
+ * </pre>
+ *
+ * @param ch the character to convert, may be null
+ * @return the escaped Unicode string, null if null input
+ */
+ public static String unicodeEscaped(Character ch) {
+ if (ch == null) {
+ return null;
+ }
+ return unicodeEscaped(ch.charValue());
+ }
+
+ //--------------------------------------------------------------------------
+ /**
+ * <p>Checks whether the character is ASCII 7 bit.</p>
+ *
+ * <pre>
+ * CharUtils.isAscii('a') = true
+ * CharUtils.isAscii('A') = true
+ * CharUtils.isAscii('3') = true
+ * CharUtils.isAscii('-') = true
+ * CharUtils.isAscii('\n') = true
+ * CharUtils.isAscii('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if less than 128
+ */
+ public static boolean isAscii(char ch) {
+ return ch < 128;
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit printable.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiPrintable('a') = true
+ * CharUtils.isAsciiPrintable('A') = true
+ * CharUtils.isAsciiPrintable('3') = true
+ * CharUtils.isAsciiPrintable('-') = true
+ * CharUtils.isAsciiPrintable('\n') = false
+ * CharUtils.isAsciiPrintable('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 32 and 126 inclusive
+ */
+ public static boolean isAsciiPrintable(char ch) {
+ return ch >= 32 && ch < 127;
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit control.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiControl('a') = false
+ * CharUtils.isAsciiControl('A') = false
+ * CharUtils.isAsciiControl('3') = false
+ * CharUtils.isAsciiControl('-') = false
+ * CharUtils.isAsciiControl('\n') = true
+ * CharUtils.isAsciiControl('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if less than 32 or equals 127
+ */
+ public static boolean isAsciiControl(char ch) {
+ return ch < 32 || ch == 127;
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit alphabetic.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiAlpha('a') = true
+ * CharUtils.isAsciiAlpha('A') = true
+ * CharUtils.isAsciiAlpha('3') = false
+ * CharUtils.isAsciiAlpha('-') = false
+ * CharUtils.isAsciiAlpha('\n') = false
+ * CharUtils.isAsciiAlpha('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 65 and 90 or 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlpha(char ch) {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit alphabetic upper case.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphaUpper('a') = false
+ * CharUtils.isAsciiAlphaUpper('A') = true
+ * CharUtils.isAsciiAlphaUpper('3') = false
+ * CharUtils.isAsciiAlphaUpper('-') = false
+ * CharUtils.isAsciiAlphaUpper('\n') = false
+ * CharUtils.isAsciiAlphaUpper('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 65 and 90 inclusive
+ */
+ public static boolean isAsciiAlphaUpper(char ch) {
+ return ch >= 'A' && ch <= 'Z';
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit alphabetic lower case.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphaLower('a') = true
+ * CharUtils.isAsciiAlphaLower('A') = false
+ * CharUtils.isAsciiAlphaLower('3') = false
+ * CharUtils.isAsciiAlphaLower('-') = false
+ * CharUtils.isAsciiAlphaLower('\n') = false
+ * CharUtils.isAsciiAlphaLower('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlphaLower(char ch) {
+ return ch >= 'a' && ch <= 'z';
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit numeric.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiNumeric('a') = false
+ * CharUtils.isAsciiNumeric('A') = false
+ * CharUtils.isAsciiNumeric('3') = true
+ * CharUtils.isAsciiNumeric('-') = false
+ * CharUtils.isAsciiNumeric('\n') = false
+ * CharUtils.isAsciiNumeric('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 48 and 57 inclusive
+ */
+ public static boolean isAsciiNumeric(char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ /**
+ * <p>Checks whether the character is ASCII 7 bit numeric.</p>
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphanumeric('a') = true
+ * CharUtils.isAsciiAlphanumeric('A') = true
+ * CharUtils.isAsciiAlphanumeric('3') = true
+ * CharUtils.isAsciiAlphanumeric('-') = false
+ * CharUtils.isAsciiAlphanumeric('\n') = false
+ * CharUtils.isAsciiAlphanumeric('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlphanumeric(char ch) {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
+ }
+
+} \ No newline at end of file
diff --git a/src/org/apache/commons/lang3/ClassUtils.java b/src/org/apache/commons/lang3/ClassUtils.java
new file mode 100644
index 0000000..032cc6d
--- /dev/null
+++ b/src/org/apache/commons/lang3/ClassUtils.java
@@ -0,0 +1,1103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * <p>Operates on classes without using reflection.</p>
+ *
+ * <p>This class handles invalid {@code null} inputs as best it can.
+ * Each method documents its behaviour in more detail.</p>
+ *
+ * <p>The notion of a {@code canonical name} includes the human
+ * readable name for the type, for example {@code int[]}. The
+ * non-canonical method variants work with the JVM names, such as
+ * {@code [I}. </p>
+ *
+ * @since 2.0
+ * @version $Id: ClassUtils.java 1145035 2011-07-11 06:09:39Z bayard $
+ */
+public class ClassUtils {
+
+ /**
+ * <p>The package separator character: <code>'&#x2e;' == {@value}</code>.</p>
+ */
+ public static final char PACKAGE_SEPARATOR_CHAR = '.';
+
+ /**
+ * <p>The package separator String: {@code "&#x2e;"}.</p>
+ */
+ public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR);
+
+ /**
+ * <p>The inner class separator character: <code>'$' == {@value}</code>.</p>
+ */
+ public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
+
+ /**
+ * <p>The inner class separator String: {@code "$"}.</p>
+ */
+ public static final String INNER_CLASS_SEPARATOR = String.valueOf(INNER_CLASS_SEPARATOR_CHAR);
+
+ /**
+ * Maps primitive {@code Class}es to their corresponding wrapper {@code Class}.
+ */
+ private static final Map<Class<?>, Class<?>> primitiveWrapperMap = new HashMap<Class<?>, Class<?>>();
+ static {
+ primitiveWrapperMap.put(Boolean.TYPE, Boolean.class);
+ primitiveWrapperMap.put(Byte.TYPE, Byte.class);
+ primitiveWrapperMap.put(Character.TYPE, Character.class);
+ primitiveWrapperMap.put(Short.TYPE, Short.class);
+ primitiveWrapperMap.put(Integer.TYPE, Integer.class);
+ primitiveWrapperMap.put(Long.TYPE, Long.class);
+ primitiveWrapperMap.put(Double.TYPE, Double.class);
+ primitiveWrapperMap.put(Float.TYPE, Float.class);
+ primitiveWrapperMap.put(Void.TYPE, Void.TYPE);
+ }
+
+ /**
+ * Maps wrapper {@code Class}es to their corresponding primitive types.
+ */
+ private static final Map<Class<?>, Class<?>> wrapperPrimitiveMap = new HashMap<Class<?>, Class<?>>();
+ static {
+ for (Class<?> primitiveClass : primitiveWrapperMap.keySet()) {
+ Class<?> wrapperClass = primitiveWrapperMap.get(primitiveClass);
+ if (!primitiveClass.equals(wrapperClass)) {
+ wrapperPrimitiveMap.put(wrapperClass, primitiveClass);
+ }
+ }
+ }
+
+ /**
+ * Maps a primitive class name to its corresponding abbreviation used in array class names.
+ */
+ private static final Map<String, String> abbreviationMap = new HashMap<String, String>();
+
+ /**
+ * Maps an abbreviation used in array class names to corresponding primitive class name.
+ */
+ private static final Map<String, String> reverseAbbreviationMap = new HashMap<String, String>();
+
+ /**
+ * Add primitive type abbreviation to maps of abbreviations.
+ *
+ * @param primitive Canonical name of primitive type
+ * @param abbreviation Corresponding abbreviation of primitive type
+ */
+ private static void addAbbreviation(String primitive, String abbreviation) {
+ abbreviationMap.put(primitive, abbreviation);
+ reverseAbbreviationMap.put(abbreviation, primitive);
+ }
+
+ /**
+ * Feed abbreviation maps
+ */
+ static {
+ addAbbreviation("int", "I");
+ addAbbreviation("boolean", "Z");
+ addAbbreviation("float", "F");
+ addAbbreviation("long", "J");
+ addAbbreviation("short", "S");
+ addAbbreviation("byte", "B");
+ addAbbreviation("double", "D");
+ addAbbreviation("char", "C");
+ }
+
+ /**
+ * <p>ClassUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as
+ * {@code ClassUtils.getShortClassName(cls)}.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public ClassUtils() {
+ super();
+ }
+
+ // Short class name
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Gets the class name minus the package name for an {@code Object}.</p>
+ *
+ * @param object the class to get the short name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the class name of the object without the package name, or the null value
+ */
+ public static String getShortClassName(Object object, String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getShortClassName(object.getClass());
+ }
+
+ /**
+ * <p>Gets the class name minus the package name from a {@code Class}.</p>
+ *
+ * <p>Consider using the Java 5 API {@link Class#getSimpleName()} instead.
+ * The one known difference is that this code will return {@code "Map.Entry"} while
+ * the {@code java.lang.Class} variant will simply return {@code "Entry"}. </p>
+ *
+ * @param cls the class to get the short name for.
+ * @return the class name without the package name or an empty string
+ */
+ public static String getShortClassName(Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getShortClassName(cls.getName());
+ }
+
+ /**
+ * <p>Gets the class name minus the package name from a String.</p>
+ *
+ * <p>The string passed in is assumed to be a class name - it is not checked.</p>
+
+ * <p>Note that this method differs from Class.getSimpleName() in that this will
+ * return {@code "Map.Entry"} whilst the {@code java.lang.Class} variant will simply
+ * return {@code "Entry"}. </p>
+ *
+ * @param className the className to get the short name for
+ * @return the class name of the class without the package name or an empty string
+ */
+ public static String getShortClassName(String className) {
+ if (className == null) {
+ return StringUtils.EMPTY;
+ }
+ if (className.length() == 0) {
+ return StringUtils.EMPTY;
+ }
+
+ StringBuilder arrayPrefix = new StringBuilder();
+
+ // Handle array encoding
+ if (className.startsWith("[")) {
+ while (className.charAt(0) == '[') {
+ className = className.substring(1);
+ arrayPrefix.append("[]");
+ }
+ // Strip Object type encoding
+ if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
+ className = className.substring(1, className.length() - 1);
+ }
+ }
+
+ if (reverseAbbreviationMap.containsKey(className)) {
+ className = reverseAbbreviationMap.get(className);
+ }
+
+ int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+ int innerIdx = className.indexOf(
+ INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
+ String out = className.substring(lastDotIdx + 1);
+ if (innerIdx != -1) {
+ out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
+ }
+ return out + arrayPrefix;
+ }
+
+ /**
+ * <p>Null-safe version of <code>aClass.getSimpleName()</code></p>
+ *
+ * @param cls the class for which to get the simple name.
+ * @return the simple class name.
+ * @since 3.0
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return cls.getSimpleName();
+ }
+
+ /**
+ * <p>Null-safe version of <code>aClass.getSimpleName()</code></p>
+ *
+ * @param object the object for which to get the simple class name.
+ * @param valueIfNull the value to return if <code>object</code> is <code>null</code>
+ * @return the simple class name.
+ * @since 3.0
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(Object object, String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getSimpleName(object.getClass());
+ }
+
+ // Package name
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Gets the package name of an {@code Object}.</p>
+ *
+ * @param object the class to get the package name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the package name of the object, or the null value
+ */
+ public static String getPackageName(Object object, String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getPackageName(object.getClass());
+ }
+
+ /**
+ * <p>Gets the package name of a {@code Class}.</p>
+ *
+ * @param cls the class to get the package name for, may be {@code null}.
+ * @return the package name or an empty string
+ */
+ public static String getPackageName(Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getPackageName(cls.getName());
+ }
+
+ /**
+ * <p>Gets the package name from a {@code String}.</p>
+ *
+ * <p>The string passed in is assumed to be a class name - it is not checked.</p>
+ * <p>If the class is unpackaged, return an empty string.</p>
+ *
+ * @param className the className to get the package name for, may be {@code null}
+ * @return the package name or an empty string
+ */
+ public static String getPackageName(String className) {
+ if (className == null || className.length() == 0) {
+ return StringUtils.EMPTY;
+ }
+
+ // Strip array encoding
+ while (className.charAt(0) == '[') {
+ className = className.substring(1);
+ }
+ // Strip Object type encoding
+ if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
+ className = className.substring(1);
+ }
+
+ int i = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+ if (i == -1) {
+ return StringUtils.EMPTY;
+ }
+ return className.substring(0, i);
+ }
+
+ // Superclasses/Superinterfaces
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Gets a {@code List} of superclasses for the given class.</p>
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @return the {@code List} of superclasses in order going up from this one
+ * {@code null} if null input
+ */
+ public static List<Class<?>> getAllSuperclasses(Class<?> cls) {
+ if (cls == null) {
+ return null;
+ }
+ List<Class<?>> classes = new ArrayList<Class<?>>();
+ Class<?> superclass = cls.getSuperclass();
+ while (superclass != null) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes;
+ }
+
+ /**
+ * <p>Gets a {@code List} of all interfaces implemented by the given
+ * class and its superclasses.</p>
+ *
+ * <p>The order is determined by looking through each interface in turn as
+ * declared in the source file and following its hierarchy up. Then each
+ * superclass is considered in the same way. Later duplicates are ignored,
+ * so the order is maintained.</p>
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @return the {@code List} of interfaces in order,
+ * {@code null} if null input
+ */
+ public static List<Class<?>> getAllInterfaces(Class<?> cls) {
+ if (cls == null) {
+ return null;
+ }
+
+ LinkedHashSet<Class<?>> interfacesFound = new LinkedHashSet<Class<?>>();
+ getAllInterfaces(cls, interfacesFound);
+
+ return new ArrayList<Class<?>>(interfacesFound);
+ }
+
+ /**
+ * Get the interfaces for the specified class.
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @param interfacesFound the {@code Set} of interfaces for the class
+ */
+ private static void getAllInterfaces(Class<?> cls, HashSet<Class<?>> interfacesFound) {
+ while (cls != null) {
+ Class<?>[] interfaces = cls.getInterfaces();
+
+ for (Class<?> i : interfaces) {
+ if (interfacesFound.add(i)) {
+ getAllInterfaces(i, interfacesFound);
+ }
+ }
+
+ cls = cls.getSuperclass();
+ }
+ }
+
+ // Convert list
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Given a {@code List} of class names, this method converts them into classes.</p>
+ *
+ * <p>A new {@code List} is returned. If the class name cannot be found, {@code null}
+ * is stored in the {@code List}. If the class name in the {@code List} is
+ * {@code null}, {@code null} is stored in the output {@code List}.</p>
+ *
+ * @param classNames the classNames to change
+ * @return a {@code List} of Class objects corresponding to the class names,
+ * {@code null} if null input
+ * @throws ClassCastException if classNames contains a non String entry
+ */
+ public static List<Class<?>> convertClassNamesToClasses(List<String> classNames) {
+ if (classNames == null) {
+ return null;
+ }
+ List<Class<?>> classes = new ArrayList<Class<?>>(classNames.size());
+ for (String className : classNames) {
+ try {
+ classes.add(Class.forName(className));
+ } catch (Exception ex) {
+ classes.add(null);
+ }
+ }
+ return classes;
+ }
+
+ /**
+ * <p>Given a {@code List} of {@code Class} objects, this method converts
+ * them into class names.</p>
+ *
+ * <p>A new {@code List} is returned. {@code null} objects will be copied into
+ * the returned list as {@code null}.</p>
+ *
+ * @param classes the classes to change
+ * @return a {@code List} of class names corresponding to the Class objects,
+ * {@code null} if null input
+ * @throws ClassCastException if {@code classes} contains a non-{@code Class} entry
+ */
+ public static List<String> convertClassesToClassNames(List<Class<?>> classes) {
+ if (classes == null) {
+ return null;
+ }
+ List<String> classNames = new ArrayList<String>(classes.size());
+ for (Class<?> cls : classes) {
+ if (cls == null) {
+ classNames.add(null);
+ } else {
+ classNames.add(cls.getName());
+ }
+ }
+ return classNames;
+ }
+
+ // Is assignable
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Checks if an array of Classes can be assigned to another array of Classes.</p>
+ *
+ * <p>This method calls {@link #isAssignable(Class, Class) isAssignable} for each
+ * Class pair in the input arrays. It can be used to check if a set of arguments
+ * (the first parameter) are suitably compatible with a set of method parameter types
+ * (the second parameter).</p>
+ *
+ * <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this
+ * method takes into account widenings of primitive classes and
+ * {@code null}s.</p>
+ *
+ * <p>Primitive widenings allow an int to be assigned to a {@code long},
+ * {@code float} or {@code double}. This method returns the correct
+ * result for these cases.</p>
+ *
+ * <p>{@code Null} may be assigned to any reference type. This method will
+ * return {@code true} if {@code null} is passed in and the toClass is
+ * non-primitive.</p>
+ *
+ * <p>Specifically, this method tests whether the type represented by the
+ * specified {@code Class} parameter can be converted to the type
+ * represented by this {@code Class} object via an identity conversion
+ * widening primitive or widening reference conversion. See
+ * <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
+ * sections 5.1.1, 5.1.2 and 5.1.4 for details.</p>
+ *
+ * <p><strong>Since Lang 3.0,</strong> this method will default behavior for
+ * calculating assignability between primitive and wrapper types <em>corresponding
+ * to the running Java version</em>; i.e. autoboxing will be the default
+ * behavior in VMs running Java versions >= 1.5.</p>
+ *
+ * @param classArray the array of Classes to check, may be {@code null}
+ * @param toClassArray the array of Classes to try to assign into, may be {@code null}
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?>[] classArray, Class<?>... toClassArray) {
+ return isAssignable(classArray, toClassArray, SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5));
+ }
+
+ /**
+ * <p>Checks if an array of Classes can be assigned to another array of Classes.</p>
+ *
+ * <p>This method calls {@link #isAssignable(Class, Class) isAssignable} for each
+ * Class pair in the input arrays. It can be used to check if a set of arguments
+ * (the first parameter) are suitably compatible with a set of method parameter types
+ * (the second parameter).</p>
+ *
+ * <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this
+ * method takes into account widenings of primitive classes and
+ * {@code null}s.</p>
+ *
+ * <p>Primitive widenings allow an int to be assigned to a {@code long},
+ * {@code float} or {@code double}. This method returns the correct
+ * result for these cases.</p>
+ *
+ * <p>{@code Null} may be assigned to any reference type. This method will
+ * return {@code true} if {@code null} is passed in and the toClass is
+ * non-primitive.</p>
+ *
+ * <p>Specifically, this method tests whether the type represented by the
+ * specified {@code Class} parameter can be converted to the type
+ * represented by this {@code Class} object via an identity conversion
+ * widening primitive or widening reference conversion. See
+ * <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
+ * sections 5.1.1, 5.1.2 and 5.1.4 for details.</p>
+ *
+ * @param classArray the array of Classes to check, may be {@code null}
+ * @param toClassArray the array of Classes to try to assign into, may be {@code null}
+ * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?>[] classArray, Class<?>[] toClassArray, boolean autoboxing) {
+ if (ArrayUtils.isSameLength(classArray, toClassArray) == false) {
+ return false;
+ }
+ if (classArray == null) {
+ classArray = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (toClassArray == null) {
+ toClassArray = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ for (int i = 0; i < classArray.length; i++) {
+ if (isAssignable(classArray[i], toClassArray[i], autoboxing) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if one {@code Class} can be assigned to a variable of
+ * another {@code Class}.</p>
+ *
+ * <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method,
+ * this method takes into account widenings of primitive classes and
+ * {@code null}s.</p>
+ *
+ * <p>Primitive widenings allow an int to be assigned to a long, float or
+ * double. This method returns the correct result for these cases.</p>
+ *
+ * <p>{@code Null} may be assigned to any reference type. This method
+ * will return {@code true} if {@code null} is passed in and the
+ * toClass is non-primitive.</p>
+ *
+ * <p>Specifically, this method tests whether the type represented by the
+ * specified {@code Class} parameter can be converted to the type
+ * represented by this {@code Class} object via an identity conversion
+ * widening primitive or widening reference conversion. See
+ * <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
+ * sections 5.1.1, 5.1.2 and 5.1.4 for details.</p>
+ *
+ * <p><strong>Since Lang 3.0,</strong> this method will default behavior for
+ * calculating assignability between primitive and wrapper types <em>corresponding
+ * to the running Java version</em>; i.e. autoboxing will be the default
+ * behavior in VMs running Java versions >= 1.5.</p>
+ *
+ * @param cls the Class to check, may be null
+ * @param toClass the Class to try to assign into, returns false if null
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?> cls, Class<?> toClass) {
+ return isAssignable(cls, toClass, SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5));
+ }
+
+ /**
+ * <p>Checks if one {@code Class} can be assigned to a variable of
+ * another {@code Class}.</p>
+ *
+ * <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method,
+ * this method takes into account widenings of primitive classes and
+ * {@code null}s.</p>
+ *
+ * <p>Primitive widenings allow an int to be assigned to a long, float or
+ * double. This method returns the correct result for these cases.</p>
+ *
+ * <p>{@code Null} may be assigned to any reference type. This method
+ * will return {@code true} if {@code null} is passed in and the
+ * toClass is non-primitive.</p>
+ *
+ * <p>Specifically, this method tests whether the type represented by the
+ * specified {@code Class} parameter can be converted to the type
+ * represented by this {@code Class} object via an identity conversion
+ * widening primitive or widening reference conversion. See
+ * <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
+ * sections 5.1.1, 5.1.2 and 5.1.4 for details.</p>
+ *
+ * @param cls the Class to check, may be null
+ * @param toClass the Class to try to assign into, returns false if null
+ * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?> cls, Class<?> toClass, boolean autoboxing) {
+ if (toClass == null) {
+ return false;
+ }
+ // have to check for null, as isAssignableFrom doesn't
+ if (cls == null) {
+ return !(toClass.isPrimitive());
+ }
+ //autoboxing:
+ if (autoboxing) {
+ if (cls.isPrimitive() && !toClass.isPrimitive()) {
+ cls = primitiveToWrapper(cls);
+ if (cls == null) {
+ return false;
+ }
+ }
+ if (toClass.isPrimitive() && !cls.isPrimitive()) {
+ cls = wrapperToPrimitive(cls);
+ if (cls == null) {
+ return false;
+ }
+ }
+ }
+ if (cls.equals(toClass)) {
+ return true;
+ }
+ if (cls.isPrimitive()) {
+ if (toClass.isPrimitive() == false) {
+ return false;
+ }
+ if (Integer.TYPE.equals(cls)) {
+ return Long.TYPE.equals(toClass)
+ || Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ if (Long.TYPE.equals(cls)) {
+ return Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ if (Boolean.TYPE.equals(cls)) {
+ return false;
+ }
+ if (Double.TYPE.equals(cls)) {
+ return false;
+ }
+ if (Float.TYPE.equals(cls)) {
+ return Double.TYPE.equals(toClass);
+ }
+ if (Character.TYPE.equals(cls)) {
+ return Integer.TYPE.equals(toClass)
+ || Long.TYPE.equals(toClass)
+ || Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ if (Short.TYPE.equals(cls)) {
+ return Integer.TYPE.equals(toClass)
+ || Long.TYPE.equals(toClass)
+ || Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ if (Byte.TYPE.equals(cls)) {
+ return Short.TYPE.equals(toClass)
+ || Integer.TYPE.equals(toClass)
+ || Long.TYPE.equals(toClass)
+ || Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ // should never get here
+ return false;
+ }
+ return toClass.isAssignableFrom(cls);
+ }
+
+ /**
+ * <p>Converts the specified primitive Class object to its corresponding
+ * wrapper Class object.</p>
+ *
+ * <p>NOTE: From v2.2, this method handles {@code Void.TYPE},
+ * returning {@code Void.TYPE}.</p>
+ *
+ * @param cls the class to convert, may be null
+ * @return the wrapper class for {@code cls} or {@code cls} if
+ * {@code cls} is not a primitive. {@code null} if null input.
+ * @since 2.1
+ */
+ public static Class<?> primitiveToWrapper(Class<?> cls) {
+ Class<?> convertedClass = cls;
+ if (cls != null && cls.isPrimitive()) {
+ convertedClass = primitiveWrapperMap.get(cls);
+ }
+ return convertedClass;
+ }
+
+ /**
+ * <p>Converts the specified array of primitive Class objects to an array of
+ * its corresponding wrapper Class objects.</p>
+ *
+ * @param classes the class array to convert, may be null or empty
+ * @return an array which contains for each given class, the wrapper class or
+ * the original class if class is not a primitive. {@code null} if null input.
+ * Empty array if an empty array passed in.
+ * @since 2.1
+ */
+ public static Class<?>[] primitivesToWrappers(Class<?>... classes) {
+ if (classes == null) {
+ return null;
+ }
+
+ if (classes.length == 0) {
+ return classes;
+ }
+
+ Class<?>[] convertedClasses = new Class[classes.length];
+ for (int i = 0; i < classes.length; i++) {
+ convertedClasses[i] = primitiveToWrapper(classes[i]);
+ }
+ return convertedClasses;
+ }
+
+ /**
+ * <p>Converts the specified wrapper class to its corresponding primitive
+ * class.</p>
+ *
+ * <p>This method is the counter part of {@code primitiveToWrapper()}.
+ * If the passed in class is a wrapper class for a primitive type, this
+ * primitive type will be returned (e.g. {@code Integer.TYPE} for
+ * {@code Integer.class}). For other classes, or if the parameter is
+ * <b>null</b>, the return value is <b>null</b>.</p>
+ *
+ * @param cls the class to convert, may be <b>null</b>
+ * @return the corresponding primitive type if {@code cls} is a
+ * wrapper class, <b>null</b> otherwise
+ * @see #primitiveToWrapper(Class)
+ * @since 2.4
+ */
+ public static Class<?> wrapperToPrimitive(Class<?> cls) {
+ return wrapperPrimitiveMap.get(cls);
+ }
+
+ /**
+ * <p>Converts the specified array of wrapper Class objects to an array of
+ * its corresponding primitive Class objects.</p>
+ *
+ * <p>This method invokes {@code wrapperToPrimitive()} for each element
+ * of the passed in array.</p>
+ *
+ * @param classes the class array to convert, may be null or empty
+ * @return an array which contains for each given class, the primitive class or
+ * <b>null</b> if the original class is not a wrapper class. {@code null} if null input.
+ * Empty array if an empty array passed in.
+ * @see #wrapperToPrimitive(Class)
+ * @since 2.4
+ */
+ public static Class<?>[] wrappersToPrimitives(Class<?>... classes) {
+ if (classes == null) {
+ return null;
+ }
+
+ if (classes.length == 0) {
+ return classes;
+ }
+
+ Class<?>[] convertedClasses = new Class[classes.length];
+ for (int i = 0; i < classes.length; i++) {
+ convertedClasses[i] = wrapperToPrimitive(classes[i]);
+ }
+ return convertedClasses;
+ }
+
+ // Inner class
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Is the specified class an inner class or static nested class.</p>
+ *
+ * @param cls the class to check, may be null
+ * @return {@code true} if the class is an inner or static nested class,
+ * false if not or {@code null}
+ */
+ public static boolean isInnerClass(Class<?> cls) {
+ return cls != null && cls.getEnclosingClass() != null;
+ }
+
+ // Class loading
+ // ----------------------------------------------------------------------
+ /**
+ * Returns the class represented by {@code className} using the
+ * {@code classLoader}. This implementation supports the syntaxes
+ * "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
+ * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param classLoader the class loader to use to load the class
+ * @param className the class name
+ * @param initialize whether the class must be initialized
+ * @return the class represented by {@code className} using the {@code classLoader}
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(
+ ClassLoader classLoader, String className, boolean initialize) throws ClassNotFoundException {
+ try {
+ Class<?> clazz;
+ if (abbreviationMap.containsKey(className)) {
+ String clsName = "[" + abbreviationMap.get(className);
+ clazz = Class.forName(clsName, initialize, classLoader).getComponentType();
+ } else {
+ clazz = Class.forName(toCanonicalName(className), initialize, classLoader);
+ }
+ return clazz;
+ } catch (ClassNotFoundException ex) {
+ // allow path separators (.) as inner class name separators
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+
+ if (lastDotIndex != -1) {
+ try {
+ return getClass(classLoader, className.substring(0, lastDotIndex) +
+ INNER_CLASS_SEPARATOR_CHAR + className.substring(lastDotIndex + 1),
+ initialize);
+ } catch (ClassNotFoundException ex2) { // NOPMD
+ // ignore exception
+ }
+ }
+
+ throw ex;
+ }
+ }
+
+ /**
+ * Returns the (initialized) class represented by {@code className}
+ * using the {@code classLoader}. This implementation supports
+ * the syntaxes "{@code java.util.Map.Entry[]}",
+ * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}",
+ * and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param classLoader the class loader to use to load the class
+ * @param className the class name
+ * @return the class represented by {@code className} using the {@code classLoader}
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(ClassLoader classLoader, String className) throws ClassNotFoundException {
+ return getClass(classLoader, className, true);
+ }
+
+ /**
+ * Returns the (initialized) class represented by {@code className}
+ * using the current thread's context class loader. This implementation
+ * supports the syntaxes "{@code java.util.Map.Entry[]}",
+ * "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}",
+ * and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param className the class name
+ * @return the class represented by {@code className} using the current thread's context class loader
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(String className) throws ClassNotFoundException {
+ return getClass(className, true);
+ }
+
+ /**
+ * Returns the class represented by {@code className} using the
+ * current thread's context class loader. This implementation supports the
+ * syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
+ * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param className the class name
+ * @param initialize whether the class must be initialized
+ * @return the class represented by {@code className} using the current thread's context class loader
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(String className, boolean initialize) throws ClassNotFoundException {
+ ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
+ ClassLoader loader = contextCL == null ? ClassUtils.class.getClassLoader() : contextCL;
+ return getClass(loader, className, initialize );
+ }
+
+ // Public method
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Returns the desired Method much like {@code Class.getMethod}, however
+ * it ensures that the returned Method is from a public class or interface and not
+ * from an anonymous inner class. This means that the Method is invokable and
+ * doesn't fall foul of Java bug
+ * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957">4071957</a>).
+ *
+ * <code><pre>Set set = Collections.unmodifiableSet(...);
+ * Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty", new Class[0]);
+ * Object result = method.invoke(set, new Object[]);</pre></code>
+ * </p>
+ *
+ * @param cls the class to check, not null
+ * @param methodName the name of the method
+ * @param parameterTypes the list of parameters
+ * @return the method
+ * @throws NullPointerException if the class is null
+ * @throws SecurityException if a a security violation occured
+ * @throws NoSuchMethodException if the method is not found in the given class
+ * or if the metothod doen't conform with the requirements
+ */
+ public static Method getPublicMethod(Class<?> cls, String methodName, Class<?>... parameterTypes)
+ throws SecurityException, NoSuchMethodException {
+
+ Method declaredMethod = cls.getMethod(methodName, parameterTypes);
+ if (Modifier.isPublic(declaredMethod.getDeclaringClass().getModifiers())) {
+ return declaredMethod;
+ }
+
+ List<Class<?>> candidateClasses = new ArrayList<Class<?>>();
+ candidateClasses.addAll(getAllInterfaces(cls));
+ candidateClasses.addAll(getAllSuperclasses(cls));
+
+ for (Class<?> candidateClass : candidateClasses) {
+ if (!Modifier.isPublic(candidateClass.getModifiers())) {
+ continue;
+ }
+ Method candidateMethod;
+ try {
+ candidateMethod = candidateClass.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException ex) {
+ continue;
+ }
+ if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) {
+ return candidateMethod;
+ }
+ }
+
+ throw new NoSuchMethodException("Can't find a public method for " +
+ methodName + " " + ArrayUtils.toString(parameterTypes));
+ }
+
+ // ----------------------------------------------------------------------
+ /**
+ * Converts a class name to a JLS style class name.
+ *
+ * @param className the class name
+ * @return the converted name
+ */
+ private static String toCanonicalName(String className) {
+ className = StringUtils.deleteWhitespace(className);
+ if (className == null) {
+ throw new NullPointerException("className must not be null.");
+ } else if (className.endsWith("[]")) {
+ StringBuilder classNameBuffer = new StringBuilder();
+ while (className.endsWith("[]")) {
+ className = className.substring(0, className.length() - 2);
+ classNameBuffer.append("[");
+ }
+ String abbreviation = abbreviationMap.get(className);
+ if (abbreviation != null) {
+ classNameBuffer.append(abbreviation);
+ } else {
+ classNameBuffer.append("L").append(className).append(";");
+ }
+ className = classNameBuffer.toString();
+ }
+ return className;
+ }
+
+ /**
+ * <p>Converts an array of {@code Object} in to an array of {@code Class} objects.
+ * If any of these objects is null, a null element will be inserted into the array.</p>
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array an {@code Object} array
+ * @return a {@code Class} array, {@code null} if null array input
+ * @since 2.4
+ */
+ public static Class<?>[] toClass(Object... array) {
+ if (array == null) {
+ return null;
+ } else if (array.length == 0) {
+ return ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ Class<?>[] classes = new Class[array.length];
+ for (int i = 0; i < array.length; i++) {
+ classes[i] = array[i] == null ? null : array[i].getClass();
+ }
+ return classes;
+ }
+
+ // Short canonical name
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Gets the canonical name minus the package name for an {@code Object}.</p>
+ *
+ * @param object the class to get the short name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the canonical name of the object without the package name, or the null value
+ * @since 2.4
+ */
+ public static String getShortCanonicalName(Object object, String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getShortCanonicalName(object.getClass().getName());
+ }
+
+ /**
+ * <p>Gets the canonical name minus the package name from a {@code Class}.</p>
+ *
+ * @param cls the class to get the short name for.
+ * @return the canonical name without the package name or an empty string
+ * @since 2.4
+ */
+ public static String getShortCanonicalName(Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getShortCanonicalName(cls.getName());
+ }
+
+ /**
+ * <p>Gets the canonical name minus the package name from a String.</p>
+ *
+ * <p>The string passed in is assumed to be a canonical name - it is not checked.</p>
+ *
+ * @param canonicalName the class name to get the short name for
+ * @return the canonical name of the class without the package name or an empty string
+ * @since 2.4
+ */
+ public static String getShortCanonicalName(String canonicalName) {
+ return ClassUtils.getShortClassName(getCanonicalName(canonicalName));
+ }
+
+ // Package name
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Gets the package name from the canonical name of an {@code Object}.</p>
+ *
+ * @param object the class to get the package name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the package name of the object, or the null value
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(Object object, String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getPackageCanonicalName(object.getClass().getName());
+ }
+
+ /**
+ * <p>Gets the package name from the canonical name of a {@code Class}.</p>
+ *
+ * @param cls the class to get the package name for, may be {@code null}.
+ * @return the package name or an empty string
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getPackageCanonicalName(cls.getName());
+ }
+
+ /**
+ * <p>Gets the package name from the canonical name. </p>
+ *
+ * <p>The string passed in is assumed to be a canonical name - it is not checked.</p>
+ * <p>If the class is unpackaged, return an empty string.</p>
+ *
+ * @param canonicalName the canonical name to get the package name for, may be {@code null}
+ * @return the package name or an empty string
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(String canonicalName) {
+ return ClassUtils.getPackageName(getCanonicalName(canonicalName));
+ }
+
+ /**
+ * <p>Converts a given name of class into canonical format.
+ * If name of class is not a name of array class it returns
+ * unchanged name.</p>
+ * <p>Example:
+ * <ul>
+ * <li>{@code getCanonicalName("[I") = "int[]"}</li>
+ * <li>{@code getCanonicalName("[Ljava.lang.String;") = "java.lang.String[]"}</li>
+ * <li>{@code getCanonicalName("java.lang.String") = "java.lang.String"}</li>
+ * </ul>
+ * </p>
+ *
+ * @param className the name of class
+ * @return canonical form of class name
+ * @since 2.4
+ */
+ private static String getCanonicalName(String className) {
+ className = StringUtils.deleteWhitespace(className);
+ if (className == null) {
+ return null;
+ } else {
+ int dim = 0;
+ while (className.startsWith("[")) {
+ dim++;
+ className = className.substring(1);
+ }
+ if (dim < 1) {
+ return className;
+ } else {
+ if (className.startsWith("L")) {
+ className = className.substring(
+ 1,
+ className.endsWith(";")
+ ? className.length() - 1
+ : className.length());
+ } else {
+ if (className.length() > 0) {
+ className = reverseAbbreviationMap.get(className.substring(0, 1));
+ }
+ }
+ StringBuilder canonicalClassNameBuffer = new StringBuilder(className);
+ for (int i = 0; i < dim; i++) {
+ canonicalClassNameBuffer.append("[]");
+ }
+ return canonicalClassNameBuffer.toString();
+ }
+ }
+ }
+}
diff --git a/src/org/apache/commons/lang3/JavaVersion.java b/src/org/apache/commons/lang3/JavaVersion.java
new file mode 100644
index 0000000..4901f6d
--- /dev/null
+++ b/src/org/apache/commons/lang3/JavaVersion.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+/**
+ * <p>An enum representing all the versions of the Java specification.
+ * This is intended to mirror available values from the
+ * <em>java.specification.version</em> System property. </p>
+ *
+ * @since 3.0
+ * @version $Id: $
+ */
+public enum JavaVersion {
+
+ /**
+ * The Java version reported by Android. This is not an official Java version number.
+ */
+ JAVA_0_9(1.5f, "0.9"),
+
+ /**
+ * Java 1.1.
+ */
+ JAVA_1_1(1.1f, "1.1"),
+
+ /**
+ * Java 1.2.
+ */
+ JAVA_1_2(1.2f, "1.2"),
+
+ /**
+ * Java 1.3.
+ */
+ JAVA_1_3(1.3f, "1.3"),
+
+ /**
+ * Java 1.4.
+ */
+ JAVA_1_4(1.4f, "1.4"),
+
+ /**
+ * Java 1.5.
+ */
+ JAVA_1_5(1.5f, "1.5"),
+
+ /**
+ * Java 1.6.
+ */
+ JAVA_1_6(1.6f, "1.6"),
+
+ /**
+ * Java 1.7.
+ */
+ JAVA_1_7(1.7f, "1.7"),
+
+ /**
+ * Java 1.8.
+ */
+ JAVA_1_8(1.8f, "1.8");
+
+ /**
+ * The float value.
+ */
+ private float value;
+ /**
+ * The standard name.
+ */
+ private String name;
+
+ /**
+ * Constructor.
+ *
+ * @param value the float value
+ * @param name the standard name, not null
+ */
+ JavaVersion(final float value, final String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Whether this version of Java is at least the version of Java passed in.</p>
+ *
+ * <p>For example:<br />
+ * {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}<p>
+ *
+ * @param requiredVersion the version to check against, not null
+ * @return true if this version is equal to or greater than the specified version
+ */
+ public boolean atLeast(JavaVersion requiredVersion) {
+ return this.value >= requiredVersion.value;
+ }
+
+ /**
+ * Transforms the given string with a Java version number to the
+ * corresponding constant of this enumeration class. This method is used
+ * internally.
+ *
+ * @param nom the Java version as string
+ * @return the corresponding enumeration constant or <b>null</b> if the
+ * version is unknown
+ */
+ // helper for static importing
+ static JavaVersion getJavaVersion(final String nom) {
+ return get(nom);
+ }
+
+ /**
+ * Transforms the given string with a Java version number to the
+ * corresponding constant of this enumeration class. This method is used
+ * internally.
+ *
+ * @param nom the Java version as string
+ * @return the corresponding enumeration constant or <b>null</b> if the
+ * version is unknown
+ */
+ static JavaVersion get(final String nom) {
+ if ("0.9".equals(nom)) {
+ return JAVA_0_9;
+ } else if ("1.1".equals(nom)) {
+ return JAVA_1_1;
+ } else if ("1.2".equals(nom)) {
+ return JAVA_1_2;
+ } else if ("1.3".equals(nom)) {
+ return JAVA_1_3;
+ } else if ("1.4".equals(nom)) {
+ return JAVA_1_4;
+ } else if ("1.5".equals(nom)) {
+ return JAVA_1_5;
+ } else if ("1.6".equals(nom)) {
+ return JAVA_1_6;
+ } else if ("1.7".equals(nom)) {
+ return JAVA_1_7;
+ } else if ("1.8".equals(nom)) {
+ return JAVA_1_8;
+ } else {
+ return null;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>The string value is overridden to return the standard name.</p>
+ *
+ * <p>For example, <code>"1.5"</code>.</p>
+ *
+ * @return the name, not null
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/ObjectUtils.java b/src/org/apache/commons/lang3/ObjectUtils.java
new file mode 100644
index 0000000..69dde6f
--- /dev/null
+++ b/src/org/apache/commons/lang3/ObjectUtils.java
@@ -0,0 +1,608 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.exception.CloneFailedException;
+import org.apache.commons.lang3.mutable.MutableInt;
+
+/**
+ * <p>Operations on {@code Object}.</p>
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will generally not be thrown for a {@code null} input.
+ * Each method documents its behaviour in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ * @version $Id: ObjectUtils.java 1153350 2011-08-03 05:29:21Z bayard $
+ */
+//@Immutable
+public class ObjectUtils {
+
+ /**
+ * <p>Singleton used as a {@code null} placeholder where
+ * {@code null} has another meaning.</p>
+ *
+ * <p>For example, in a {@code HashMap} the
+ * {@link java.util.HashMap#get(java.lang.Object)} method returns
+ * {@code null} if the {@code Map} contains {@code null} or if there
+ * is no matching key. The {@code Null} placeholder can be used to
+ * distinguish between these two cases.</p>
+ *
+ * <p>Another example is {@code Hashtable}, where {@code null}
+ * cannot be stored.</p>
+ *
+ * <p>This instance is Serializable.</p>
+ */
+ public static final Null NULL = new Null();
+
+ /**
+ * <p>{@code ObjectUtils} instances should NOT be constructed in
+ * standard programming. Instead, the static methods on the class should
+ * be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public ObjectUtils() {
+ super();
+ }
+
+ // Defaulting
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns a default value if the object passed is {@code null}.</p>
+ *
+ * <pre>
+ * ObjectUtils.defaultIfNull(null, null) = null
+ * ObjectUtils.defaultIfNull(null, "") = ""
+ * ObjectUtils.defaultIfNull(null, "zz") = "zz"
+ * ObjectUtils.defaultIfNull("abc", *) = "abc"
+ * ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
+ * </pre>
+ *
+ * @param <T> the type of the object
+ * @param object the {@code Object} to test, may be {@code null}
+ * @param defaultValue the default value to return, may be {@code null}
+ * @return {@code object} if it is not {@code null}, defaultValue otherwise
+ */
+ public static <T> T defaultIfNull(T object, T defaultValue) {
+ return object != null ? object : defaultValue;
+ }
+
+ /**
+ * <p>Returns the first value in the array which is not {@code null}.
+ * If all the values are {@code null} or the array is {@code null}
+ * or empty then {@code null} is returned.</p>
+ *
+ * <pre>
+ * ObjectUtils.firstNonNull(null, null) = null
+ * ObjectUtils.firstNonNull(null, "") = ""
+ * ObjectUtils.firstNonNull(null, null, "") = ""
+ * ObjectUtils.firstNonNull(null, "zz") = "zz"
+ * ObjectUtils.firstNonNull("abc", *) = "abc"
+ * ObjectUtils.firstNonNull(null, "xyz", *) = "xyz"
+ * ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
+ * ObjectUtils.firstNonNull() = null
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param values the values to test, may be {@code null} or empty
+ * @return the first value from {@code values} which is not {@code null},
+ * or {@code null} if there are no non-null values
+ * @since 3.0
+ */
+ public static <T> T firstNonNull(T... values) {
+ if (values != null) {
+ for (T val : values) {
+ if (val != null) {
+ return val;
+ }
+ }
+ }
+ return null;
+ }
+
+ // Null-safe equals/hashCode
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares two objects for equality, where either one or both
+ * objects may be {@code null}.</p>
+ *
+ * <pre>
+ * ObjectUtils.equals(null, null) = true
+ * ObjectUtils.equals(null, "") = false
+ * ObjectUtils.equals("", null) = false
+ * ObjectUtils.equals("", "") = true
+ * ObjectUtils.equals(Boolean.TRUE, null) = false
+ * ObjectUtils.equals(Boolean.TRUE, "true") = false
+ * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE) = true
+ * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
+ * </pre>
+ *
+ * @param object1 the first object, may be {@code null}
+ * @param object2 the second object, may be {@code null}
+ * @return {@code true} if the values of both objects are the same
+ */
+ public static boolean equals(Object object1, Object object2) {
+ if (object1 == object2) {
+ return true;
+ }
+ if ((object1 == null) || (object2 == null)) {
+ return false;
+ }
+ return object1.equals(object2);
+ }
+
+ /**
+ * <p>Compares two objects for inequality, where either one or both
+ * objects may be {@code null}.</p>
+ *
+ * <pre>
+ * ObjectUtils.notEqual(null, null) = false
+ * ObjectUtils.notEqual(null, "") = true
+ * ObjectUtils.notEqual("", null) = true
+ * ObjectUtils.notEqual("", "") = false
+ * ObjectUtils.notEqual(Boolean.TRUE, null) = true
+ * ObjectUtils.notEqual(Boolean.TRUE, "true") = true
+ * ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE) = false
+ * ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
+ * </pre>
+ *
+ * @param object1 the first object, may be {@code null}
+ * @param object2 the second object, may be {@code null}
+ * @return {@code false} if the values of both objects are the same
+ */
+ public static boolean notEqual(Object object1, Object object2) {
+ return ObjectUtils.equals(object1, object2) == false;
+ }
+
+ /**
+ * <p>Gets the hash code of an object returning zero when the
+ * object is {@code null}.</p>
+ *
+ * <pre>
+ * ObjectUtils.hashCode(null) = 0
+ * ObjectUtils.hashCode(obj) = obj.hashCode()
+ * </pre>
+ *
+ * @param obj the object to obtain the hash code of, may be {@code null}
+ * @return the hash code of the object, or zero if null
+ * @since 2.1
+ */
+ public static int hashCode(Object obj) {
+ // hashCode(Object) retained for performance, as hash code is often critical
+ return (obj == null) ? 0 : obj.hashCode();
+ }
+
+ /**
+ * <p>Gets the hash code for multiple objects.</p>
+ *
+ * <p>This allows a hash code to be rapidly calculated for a number of objects.
+ * The hash code for a single object is the <em>not</em> same as {@link #hashCode(Object)}.
+ * The hash code for multiple objects is the same as that calculated by an
+ * {@code ArrayList} containing the specified objects.</p>
+ *
+ * <pre>
+ * ObjectUtils.hashCodeMulti() = 1
+ * ObjectUtils.hashCodeMulti((Object[]) null) = 1
+ * ObjectUtils.hashCodeMulti(a) = 31 + a.hashCode()
+ * ObjectUtils.hashCodeMulti(a,b) = (31 + a.hashCode()) * 31 + b.hashCode()
+ * ObjectUtils.hashCodeMulti(a,b,c) = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
+ * </pre>
+ *
+ * @param objects the objects to obtain the hash code of, may be {@code null}
+ * @return the hash code of the objects, or zero if null
+ * @since 3.0
+ */
+ public static int hashCodeMulti(Object... objects) {
+ int hash = 1;
+ if (objects != null) {
+ for (Object object : objects) {
+ hash = hash * 31 + ObjectUtils.hashCode(object);
+ }
+ }
+ return hash;
+ }
+
+ // Identity ToString
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the toString that would be produced by {@code Object}
+ * if a class did not override toString itself. {@code null}
+ * will return {@code null}.</p>
+ *
+ * <pre>
+ * ObjectUtils.identityToString(null) = null
+ * ObjectUtils.identityToString("") = "java.lang.String@1e23"
+ * ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
+ * </pre>
+ *
+ * @param object the object to create a toString for, may be
+ * {@code null}
+ * @return the default toString text, or {@code null} if
+ * {@code null} passed in
+ */
+ public static String identityToString(Object object) {
+ if (object == null) {
+ return null;
+ }
+ StringBuffer buffer = new StringBuffer();
+ identityToString(buffer, object);
+ return buffer.toString();
+ }
+
+ /**
+ * <p>Appends the toString that would be produced by {@code Object}
+ * if a class did not override toString itself. {@code null}
+ * will throw a NullPointerException for either of the two parameters. </p>
+ *
+ * <pre>
+ * ObjectUtils.identityToString(buf, "") = buf.append("java.lang.String@1e23"
+ * ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa"
+ * ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
+ * </pre>
+ *
+ * @param buffer the buffer to append to
+ * @param object the object to create a toString for
+ * @since 2.4
+ */
+ public static void identityToString(StringBuffer buffer, Object object) {
+ if (object == null) {
+ throw new NullPointerException("Cannot get the toString of a null identity");
+ }
+ buffer.append(object.getClass().getName())
+ .append('@')
+ .append(Integer.toHexString(System.identityHashCode(object)));
+ }
+
+ // ToString
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the {@code toString} of an {@code Object} returning
+ * an empty string ("") if {@code null} input.</p>
+ *
+ * <pre>
+ * ObjectUtils.toString(null) = ""
+ * ObjectUtils.toString("") = ""
+ * ObjectUtils.toString("bat") = "bat"
+ * ObjectUtils.toString(Boolean.TRUE) = "true"
+ * </pre>
+ *
+ * @see StringUtils#defaultString(String)
+ * @see String#valueOf(Object)
+ * @param obj the Object to {@code toString}, may be null
+ * @return the passed in Object's toString, or nullStr if {@code null} input
+ * @since 2.0
+ */
+ public static String toString(Object obj) {
+ return obj == null ? "" : obj.toString();
+ }
+
+ /**
+ * <p>Gets the {@code toString} of an {@code Object} returning
+ * a specified text if {@code null} input.</p>
+ *
+ * <pre>
+ * ObjectUtils.toString(null, null) = null
+ * ObjectUtils.toString(null, "null") = "null"
+ * ObjectUtils.toString("", "null") = ""
+ * ObjectUtils.toString("bat", "null") = "bat"
+ * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
+ * </pre>
+ *
+ * @see StringUtils#defaultString(String,String)
+ * @see String#valueOf(Object)
+ * @param obj the Object to {@code toString}, may be null
+ * @param nullStr the String to return if {@code null} input, may be null
+ * @return the passed in Object's toString, or nullStr if {@code null} input
+ * @since 2.0
+ */
+ public static String toString(Object obj, String nullStr) {
+ return obj == null ? nullStr : obj.toString();
+ }
+
+ // Comparable
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Null safe comparison of Comparables.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param values the set of comparable values, may be null
+ * @return
+ * <ul>
+ * <li>If any objects are non-null and unequal, the lesser object.
+ * <li>If all objects are non-null and equal, the first.
+ * <li>If any of the comparables are null, the lesser of the non-null objects.
+ * <li>If all the comparables are null, null is returned.
+ * </ul>
+ */
+ public static <T extends Comparable<? super T>> T min(T... values) {
+ T result = null;
+ if (values != null) {
+ for (T value : values) {
+ if (compare(value, result, true) < 0) {
+ result = value;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * <p>Null safe comparison of Comparables.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param values the set of comparable values, may be null
+ * @return
+ * <ul>
+ * <li>If any objects are non-null and unequal, the greater object.
+ * <li>If all objects are non-null and equal, the first.
+ * <li>If any of the comparables are null, the greater of the non-null objects.
+ * <li>If all the comparables are null, null is returned.
+ * </ul>
+ */
+ public static <T extends Comparable<? super T>> T max(T... values) {
+ T result = null;
+ if (values != null) {
+ for (T value : values) {
+ if (compare(value, result, false) > 0) {
+ result = value;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * <p>Null safe comparison of Comparables.
+ * {@code null} is assumed to be less than a non-{@code null} value.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param c1 the first comparable, may be null
+ * @param c2 the second comparable, may be null
+ * @return a negative value if c1 < c2, zero if c1 = c2
+ * and a positive value if c1 > c2
+ */
+ public static <T extends Comparable<? super T>> int compare(T c1, T c2) {
+ return compare(c1, c2, false);
+ }
+
+ /**
+ * <p>Null safe comparison of Comparables.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param c1 the first comparable, may be null
+ * @param c2 the second comparable, may be null
+ * @param nullGreater if true {@code null} is considered greater
+ * than a non-{@code null} value or if false {@code null} is
+ * considered less than a Non-{@code null} value
+ * @return a negative value if c1 < c2, zero if c1 = c2
+ * and a positive value if c1 > c2
+ * @see java.util.Comparator#compare(Object, Object)
+ */
+ public static <T extends Comparable<? super T>> int compare(T c1, T c2, boolean nullGreater) {
+ if (c1 == c2) {
+ return 0;
+ } else if (c1 == null) {
+ return (nullGreater ? 1 : -1);
+ } else if (c2 == null) {
+ return (nullGreater ? -1 : 1);
+ }
+ return c1.compareTo(c2);
+ }
+
+ /**
+ * Find the "best guess" middle value among comparables. If there is an even
+ * number of total values, the lower of the two middle values will be returned.
+ * @param <T> type of values processed by this method
+ * @param items to compare
+ * @return T at middle position
+ * @throws NullPointerException if items is {@code null}
+ * @throws IllegalArgumentException if items is empty or contains {@code null} values
+ * @since 3.0.1
+ */
+ public static <T extends Comparable<? super T>> T median(T... items) {
+ Validate.notEmpty(items);
+ Validate.noNullElements(items);
+ TreeSet<T> sort = new TreeSet<T>();
+ Collections.addAll(sort, items);
+ @SuppressWarnings("unchecked") //we know all items added were T instances
+ T result = (T) sort.toArray()[(sort.size() - 1) / 2];
+ return result;
+ }
+
+ /**
+ * Find the "best guess" middle value among comparables. If there is an even
+ * number of total values, the lower of the two middle values will be returned.
+ * @param <T> type of values processed by this method
+ * @param comparator to use for comparisons
+ * @param items to compare
+ * @return T at middle position
+ * @throws NullPointerException if items or comparator is {@code null}
+ * @throws IllegalArgumentException if items is empty or contains {@code null} values
+ * @since 3.0.1
+ */
+ public static <T> T median(Comparator<T> comparator, T... items) {
+ Validate.notEmpty(items, "null/empty items");
+ Validate.noNullElements(items);
+ Validate.notNull(comparator, "null comparator");
+ TreeSet<T> sort = new TreeSet<T>(comparator);
+ Collections.addAll(sort, items);
+ @SuppressWarnings("unchecked") //we know all items added were T instances
+ T result = (T) sort.toArray()[(sort.size() - 1) / 2];
+ return result;
+ }
+
+ // Mode
+ //-----------------------------------------------------------------------
+ /**
+ * Find the most frequently occurring item.
+ *
+ * @param <T> type of values processed by this method
+ * @param items to check
+ * @return most populous T, {@code null} if non-unique or no items supplied
+ * @since 3.0.1
+ */
+ public static <T> T mode(T... items) {
+ if (ArrayUtils.isNotEmpty(items)) {
+ HashMap<T, MutableInt> occurrences = new HashMap<T, MutableInt>(items.length);
+ for (T t : items) {
+ MutableInt count = occurrences.get(t);
+ if (count == null) {
+ occurrences.put(t, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ T result = null;
+ int max = 0;
+ for (Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
+ int cmp = e.getValue().intValue();
+ if (cmp == max) {
+ result = null;
+ } else if (cmp > max) {
+ max = cmp;
+ result = e.getKey();
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ // cloning
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Clone an object.</p>
+ *
+ * @param <T> the type of the object
+ * @param obj the object to clone, null returns null
+ * @return the clone if the object implements {@link Cloneable} otherwise {@code null}
+ * @throws CloneFailedException if the object is cloneable and the clone operation fails
+ * @since 3.0
+ */
+ public static <T> T clone(final T obj) {
+ if (obj instanceof Cloneable) {
+ final Object result;
+ if (obj.getClass().isArray()) {
+ final Class<?> componentType = obj.getClass().getComponentType();
+ if (!componentType.isPrimitive()) {
+ result = ((Object[]) obj).clone();
+ } else {
+ int length = Array.getLength(obj);
+ result = Array.newInstance(componentType, length);
+ while (length-- > 0) {
+ Array.set(result, length, Array.get(obj, length));
+ }
+ }
+ } else {
+ try {
+ final Method clone = obj.getClass().getMethod("clone");
+ result = clone.invoke(obj);
+ } catch (final NoSuchMethodException e) {
+ throw new CloneFailedException("Cloneable type "
+ + obj.getClass().getName()
+ + " has no clone method", e);
+ } catch (final IllegalAccessException e) {
+ throw new CloneFailedException("Cannot clone Cloneable type "
+ + obj.getClass().getName(), e);
+ } catch (final InvocationTargetException e) {
+ throw new CloneFailedException("Exception cloning Cloneable type "
+ + obj.getClass().getName(), e.getCause());
+ }
+ }
+ @SuppressWarnings("unchecked")
+ final T checked = (T) result;
+ return checked;
+ }
+
+ return null;
+ }
+
+ /**
+ * <p>Clone an object if possible.</p>
+ *
+ * <p>This method is similar to {@link #clone(Object)}, but will return the provided
+ * instance as the return value instead of {@code null} if the instance
+ * is not cloneable. This is more convenient if the caller uses different
+ * implementations (e.g. of a service) and some of the implementations do not allow concurrent
+ * processing or have state. In such cases the implementation can simply provide a proper
+ * clone implementation and the caller's code does not have to change.</p>
+ *
+ * @param <T> the type of the object
+ * @param obj the object to clone, null returns null
+ * @return the clone if the object implements {@link Cloneable} otherwise the object itself
+ * @throws CloneFailedException if the object is cloneable and the clone operation fails
+ * @since 3.0
+ */
+ public static <T> T cloneIfPossible(final T obj) {
+ final T clone = clone(obj);
+ return clone == null ? obj : clone;
+ }
+
+ // Null
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Class used as a null placeholder where {@code null}
+ * has another meaning.</p>
+ *
+ * <p>For example, in a {@code HashMap} the
+ * {@link java.util.HashMap#get(java.lang.Object)} method returns
+ * {@code null} if the {@code Map} contains {@code null} or if there is
+ * no matching key. The {@code Null} placeholder can be used to distinguish
+ * between these two cases.</p>
+ *
+ * <p>Another example is {@code Hashtable}, where {@code null}
+ * cannot be stored.</p>
+ */
+ public static class Null implements Serializable {
+ /**
+ * Required for serialization support. Declare serialization compatibility with Commons Lang 1.0
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 7092611880189329093L;
+
+ /**
+ * Restricted constructor - singleton.
+ */
+ Null() {
+ super();
+ }
+
+ /**
+ * <p>Ensure singleton.</p>
+ *
+ * @return the singleton value
+ */
+ private Object readResolve() {
+ return ObjectUtils.NULL;
+ }
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/StringUtils.java b/src/org/apache/commons/lang3/StringUtils.java
new file mode 100644
index 0000000..0960e1a
--- /dev/null
+++ b/src/org/apache/commons/lang3/StringUtils.java
@@ -0,0 +1,6557 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Operations on {@link java.lang.String} that are
+ * {@code null} safe.</p>
+ *
+ * <ul>
+ * <li><b>IsEmpty/IsBlank</b>
+ * - checks if a String contains text</li>
+ * <li><b>Trim/Strip</b>
+ * - removes leading and trailing whitespace</li>
+ * <li><b>Equals</b>
+ * - compares two strings null-safe</li>
+ * <li><b>startsWith</b>
+ * - check if a String starts with a prefix null-safe</li>
+ * <li><b>endsWith</b>
+ * - check if a String ends with a suffix null-safe</li>
+ * <li><b>IndexOf/LastIndexOf/Contains</b>
+ * - null-safe index-of checks
+ * <li><b>IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut</b>
+ * - index-of any of a set of Strings</li>
+ * <li><b>ContainsOnly/ContainsNone/ContainsAny</b>
+ * - does String contains only/none/any of these characters</li>
+ * <li><b>Substring/Left/Right/Mid</b>
+ * - null-safe substring extractions</li>
+ * <li><b>SubstringBefore/SubstringAfter/SubstringBetween</b>
+ * - substring extraction relative to other strings</li>
+ * <li><b>Split/Join</b>
+ * - splits a String into an array of substrings and vice versa</li>
+ * <li><b>Remove/Delete</b>
+ * - removes part of a String</li>
+ * <li><b>Replace/Overlay</b>
+ * - Searches a String and replaces one String with another</li>
+ * <li><b>Chomp/Chop</b>
+ * - removes the last part of a String</li>
+ * <li><b>LeftPad/RightPad/Center/Repeat</b>
+ * - pads a String</li>
+ * <li><b>UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize</b>
+ * - changes the case of a String</li>
+ * <li><b>CountMatches</b>
+ * - counts the number of occurrences of one String in another</li>
+ * <li><b>IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable</b>
+ * - checks the characters in a String</li>
+ * <li><b>DefaultString</b>
+ * - protects against a null input String</li>
+ * <li><b>Reverse/ReverseDelimited</b>
+ * - reverses a String</li>
+ * <li><b>Abbreviate</b>
+ * - abbreviates a string using ellipsis</li>
+ * <li><b>Difference</b>
+ * - compares Strings and reports on their differences</li>
+ * <li><b>LevenshteinDistance</b>
+ * - the number of changes needed to change one String into another</li>
+ * </ul>
+ *
+ * <p>The {@code StringUtils} class defines certain words related to
+ * String handling.</p>
+ *
+ * <ul>
+ * <li>null - {@code null}</li>
+ * <li>empty - a zero-length string ({@code ""})</li>
+ * <li>space - the space character ({@code ' '}, char 32)</li>
+ * <li>whitespace - the characters defined by {@link Character#isWhitespace(char)}</li>
+ * <li>trim - the characters &lt;= 32 as in {@link String#trim()}</li>
+ * </ul>
+ *
+ * <p>{@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.</p>
+ *
+ * <p>A side effect of the {@code null} handling is that a
+ * {@code NullPointerException} should be considered a bug in
+ * {@code StringUtils}.</p>
+ *
+ * <p>Methods in this class give sample code to explain their operation.
+ * The symbol {@code *} is used to indicate any input including {@code null}.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @see java.lang.String
+ * @since 1.0
+ * @version $Id$
+ */
+//@Immutable
+public class StringUtils {
+ // Performance testing notes (JDK 1.4, Jul03, scolebourne)
+ // Whitespace:
+ // Character.isWhitespace() is faster than WHITESPACE.indexOf()
+ // where WHITESPACE is a string of all whitespace characters
+ //
+ // Character access:
+ // String.charAt(n) versus toCharArray(), then array[n]
+ // String.charAt(n) is about 15% worse for a 10K string
+ // They are about equal for a length 50 string
+ // String.charAt(n) is about 4 times better for a length 3 string
+ // String.charAt(n) is best bet overall
+ //
+ // Append:
+ // String.concat about twice as fast as StringBuffer.append
+ // (not sure who tested this)
+
+ /**
+ * The empty String {@code ""}.
+ * @since 2.0
+ */
+ public static final String EMPTY = "";
+
+ /**
+ * Represents a failed index search.
+ * @since 2.1
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * <p>The maximum size to which the padding constant(s) can expand.</p>
+ */
+ private static final int PAD_LIMIT = 8192;
+
+ /**
+ * A regex pattern for recognizing blocks of whitespace characters.
+ */
+ private static final Pattern WHITESPACE_BLOCK = Pattern.compile("\\s+");
+
+ /**
+ * <p>{@code StringUtils} instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * {@code StringUtils.trim(" foo ");}.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public StringUtils() {
+ super();
+ }
+
+ // Empty checks
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if a CharSequence is empty ("") or null.</p>
+ *
+ * <pre>
+ * StringUtils.isEmpty(null) = true
+ * StringUtils.isEmpty("") = true
+ * StringUtils.isEmpty(" ") = false
+ * StringUtils.isEmpty("bob") = false
+ * StringUtils.isEmpty(" bob ") = false
+ * </pre>
+ *
+ * <p>NOTE: This method changed in Lang version 2.0.
+ * It no longer trims the CharSequence.
+ * That functionality is available in isBlank().</p>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is empty or null
+ * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)
+ */
+ public static boolean isEmpty(CharSequence cs) {
+ return cs == null || cs.length() == 0;
+ }
+
+ /**
+ * <p>Checks if a CharSequence is not empty ("") and not null.</p>
+ *
+ * <pre>
+ * StringUtils.isNotEmpty(null) = false
+ * StringUtils.isNotEmpty("") = false
+ * StringUtils.isNotEmpty(" ") = true
+ * StringUtils.isNotEmpty("bob") = true
+ * StringUtils.isNotEmpty(" bob ") = true
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ return !StringUtils.isEmpty(cs);
+ }
+
+ /**
+ * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p>
+ *
+ * <pre>
+ * StringUtils.isBlank(null) = true
+ * StringUtils.isBlank("") = true
+ * StringUtils.isBlank(" ") = true
+ * StringUtils.isBlank("bob") = false
+ * StringUtils.isBlank(" bob ") = false
+ * </pre>
+ *
+ * @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(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;
+ }
+
+ /**
+ * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p>
+ *
+ * <pre>
+ * StringUtils.isNotBlank(null) = false
+ * StringUtils.isNotBlank("") = false
+ * StringUtils.isNotBlank(" ") = false
+ * StringUtils.isNotBlank("bob") = true
+ * StringUtils.isNotBlank(" bob ") = true
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ return !StringUtils.isBlank(cs);
+ }
+
+ // Trim
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Removes control characters (char &lt;= 32) from both
+ * ends of this String, handling {@code null} by returning
+ * {@code null}.</p>
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #strip(String)}.</p>
+ *
+ * <p>To trim your choice of characters, use the
+ * {@link #strip(String, String)} methods.</p>
+ *
+ * <pre>
+ * StringUtils.trim(null) = null
+ * StringUtils.trim("") = ""
+ * StringUtils.trim(" ") = ""
+ * StringUtils.trim("abc") = "abc"
+ * StringUtils.trim(" abc ") = "abc"
+ * </pre>
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed string, {@code null} if null String input
+ */
+ public static String trim(String str) {
+ return str == null ? null : str.trim();
+ }
+
+ /**
+ * <p>Removes control characters (char &lt;= 32) from both
+ * ends of this String returning {@code null} if the String is
+ * empty ("") after the trim or if it is {@code null}.
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #stripToNull(String)}.</p>
+ *
+ * <pre>
+ * StringUtils.trimToNull(null) = null
+ * StringUtils.trimToNull("") = null
+ * StringUtils.trimToNull(" ") = null
+ * StringUtils.trimToNull("abc") = "abc"
+ * StringUtils.trimToNull(" abc ") = "abc"
+ * </pre>
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String,
+ * {@code null} if only chars &lt;= 32, empty or null String input
+ * @since 2.0
+ */
+ public static String trimToNull(String str) {
+ String ts = trim(str);
+ return isEmpty(ts) ? null : ts;
+ }
+
+ /**
+ * <p>Removes control characters (char &lt;= 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}.
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #stripToEmpty(String)}.</p>
+ *
+ * <pre>
+ * StringUtils.trimToEmpty(null) = ""
+ * StringUtils.trimToEmpty("") = ""
+ * StringUtils.trimToEmpty(" ") = ""
+ * StringUtils.trimToEmpty("abc") = "abc"
+ * StringUtils.trimToEmpty(" abc ") = "abc"
+ * </pre>
+ *
+ * @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(String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+
+ // Stripping
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Strips whitespace from the start and end of a String.</p>
+ *
+ * <p>This is similar to {@link #trim(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str) {
+ return strip(str, null);
+ }
+
+ /**
+ * <p>Strips whitespace from the start and end of a String returning
+ * {@code null} if the String is empty ("") after the strip.</p>
+ *
+ * <p>This is similar to {@link #trimToNull(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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.length() == 0 ? null : str;
+ }
+
+ /**
+ * <p>Strips whitespace from the start and end of a String returning
+ * an empty String if {@code null} input.</p>
+ *
+ * <p>This is similar to {@link #trimToEmpty(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str) {
+ return str == null ? EMPTY : strip(str, null);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * Alternatively use {@link #strip(String)}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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, String stripChars) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ str = stripStart(str, stripChars);
+ return stripEnd(str, stripChars);
+ }
+
+ /**
+ * <p>Strips any of a set of characters from the start of a String.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * 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 "
+ * </pre>
+ *
+ * @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(String str, 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.length() == 0) {
+ return str;
+ } else {
+ while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND)) {
+ start++;
+ }
+ }
+ return str.substring(start);
+ }
+
+ /**
+ * <p>Strips any of a set of characters from the end of a String.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, 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.length() == 0) {
+ return str;
+ } else {
+ while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND)) {
+ end--;
+ }
+ }
+ return str.substring(0, end);
+ }
+
+ // StripAll
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Strips whitespace from the start and end of every String in an array.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.stripAll(null) = null
+ * StringUtils.stripAll([]) = []
+ * StringUtils.stripAll(["abc", " abc"]) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null]) = ["abc", null]
+ * </pre>
+ *
+ * @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(String... strs) {
+ return stripAll(strs, null);
+ }
+
+ /**
+ * <p>Strips any of a set of characters from the start and end of every
+ * String in an array.</p>
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>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)}.</p>
+ *
+ * <pre>
+ * 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]
+ * </pre>
+ *
+ * @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(String[] strs, String stripChars) {
+ int strsLen;
+ if (strs == null || (strsLen = strs.length) == 0) {
+ return strs;
+ }
+ String[] newArr = new String[strsLen];
+ for (int i = 0; i < strsLen; i++) {
+ newArr[i] = strip(strs[i], stripChars);
+ }
+ return newArr;
+ }
+
+ /**
+ * <p>Removes diacritics (~= accents) from a string. The case will not be altered.</p>
+ * <p>For instance, '&agrave;' will be replaced by 'a'.</p>
+ * <p>Note that ligatures will be left as is.</p>
+ *
+ * <p>This method will use the first available implementation of:
+ * Java 6's {@link java.text.Normalizer}, Java 1.3&ndash;1.5's {@code sun.text.Normalizer}</p>
+ *
+ * <pre>
+ * StringUtils.stripAccents(null) = null
+ * StringUtils.stripAccents("") = ""
+ * StringUtils.stripAccents("control") = "control"
+ * StringUtils.stripAccents("&eacute;clair") = "eclair"
+ * </pre>
+ *
+ * @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(String input) {
+ if(input == null) {
+ return null;
+ }
+ try {
+ String result = null;
+ if (java6Available) {
+ result = removeAccentsJava6(input);
+ } else if (sunAvailable) {
+ result = removeAccentsSUN(input);
+ } else {
+ throw new UnsupportedOperationException(
+ "The stripAccents(CharSequence) method requires at least Java 1.6 or a Sun JVM");
+ }
+ // Note that none of the above methods correctly remove ligatures...
+ return result;
+ } catch(IllegalArgumentException iae) {
+ throw new RuntimeException("IllegalArgumentException occurred", iae);
+ } catch(IllegalAccessException iae) {
+ throw new RuntimeException("IllegalAccessException occurred", iae);
+ } catch(InvocationTargetException ite) {
+ throw new RuntimeException("InvocationTargetException occurred", ite);
+ } catch(SecurityException se) {
+ throw new RuntimeException("SecurityException occurred", se);
+ }
+ }
+
+ /**
+ * Use {@code java.text.Normalizer#normalize(CharSequence, Normalizer.Form)}
+ * (but be careful, this class exists in Java 1.3, with an entirely different meaning!)
+ *
+ * @param text the text to be processed
+ * @return the processed string
+ * @throws IllegalAccessException may be thrown by a reflection call
+ * @throws InvocationTargetException if a reflection call throws an exception
+ * @throws IllegalStateException if the {@code Normalizer} class is not available
+ */
+ private static String removeAccentsJava6(CharSequence text)
+ throws IllegalAccessException, InvocationTargetException {
+ /*
+ String decomposed = java.text.Normalizer.normalize(CharSequence, Normalizer.Form.NFD);
+ return java6Pattern.matcher(decomposed).replaceAll("");//$NON-NLS-1$
+ */
+ if (!java6Available || java6NormalizerFormNFD == null) {
+ throw new IllegalStateException("java.text.Normalizer is not available");
+ }
+ String result;
+ result = (String) java6NormalizeMethod.invoke(null, new Object[] {text, java6NormalizerFormNFD});
+ result = java6Pattern.matcher(result).replaceAll("");//$NON-NLS-1$
+ return result;
+ }
+
+ /**
+ * Use {@code sun.text.Normalizer#decompose(String, boolean, int)}
+ *
+ * @param text the text to be processed
+ * @return the processed string
+ * @throws IllegalAccessException may be thrown by a reflection call
+ * @throws InvocationTargetException if a reflection call throws an exception
+ * @throws IllegalStateException if the {@code Normalizer} class is not available
+ */
+ private static String removeAccentsSUN(CharSequence text)
+ throws IllegalAccessException, InvocationTargetException {
+ /*
+ String decomposed = sun.text.Normalizer.decompose(text, false, 0);
+ return sunPattern.matcher(decomposed).replaceAll("");//$NON-NLS-1$
+ */
+ if (! sunAvailable) {
+ throw new IllegalStateException("sun.text.Normalizer is not available");
+ }
+ String result;
+ result = (String) sunDecomposeMethod.invoke(null, new Object[] {text, Boolean.FALSE, Integer.valueOf(0)});
+ result = sunPattern.matcher(result).replaceAll("");//$NON-NLS-1$
+ return result;
+ }
+
+ // SUN internal, Java 1.3 -> Java 5
+ private static boolean sunAvailable = false;
+ private static Method sunDecomposeMethod = null;
+ private static final Pattern sunPattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");//$NON-NLS-1$
+ // Java 6+
+ private static boolean java6Available = false;
+ private static Method java6NormalizeMethod = null;
+ private static Object java6NormalizerFormNFD = null;
+ private static final Pattern java6Pattern = sunPattern;
+
+ static {
+ try {
+ // java.text.Normalizer.normalize(CharSequence, Normalizer.Form.NFD);
+ // Be careful not to get Java 1.3 java.text.Normalizer!
+ Class<?> normalizerFormClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("java.text.Normalizer$Form");//$NON-NLS-1$
+ java6NormalizerFormNFD = normalizerFormClass.getField("NFD").get(null);//$NON-NLS-1$
+ Class<?> normalizerClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("java.text.Normalizer");//$NON-NLS-1$
+ java6NormalizeMethod = normalizerClass.getMethod("normalize",
+ new Class[] {CharSequence.class, normalizerFormClass});//$NON-NLS-1$
+ java6Available = true;
+ } catch (ClassNotFoundException e) {
+ java6Available = false;
+ } catch (NoSuchFieldException e) {
+ java6Available = false;
+ } catch (IllegalAccessException e) {
+ java6Available = false;
+ } catch (NoSuchMethodException e) {
+ java6Available = false;
+ }
+
+ try {
+ // sun.text.Normalizer.decompose(text, false, 0);
+ Class<?> normalizerClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("sun.text.Normalizer");//$NON-NLS-1$
+ sunDecomposeMethod = normalizerClass.getMethod("decompose",
+ new Class[] {String.class, Boolean.TYPE, Integer.TYPE});//$NON-NLS-1$
+ sunAvailable = true;
+ } catch (ClassNotFoundException e) {
+ sunAvailable = false;
+ } catch (NoSuchMethodException e) {
+ sunAvailable = false;
+ } catch (java.security.AccessControlException e) {
+ // LANG-744 - thrown in Google App Engine
+ sunAvailable = false;
+ }
+ }
+
+ // Equals
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares two CharSequences, returning {@code true} if they are equal.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @see java.lang.String#equals(Object)
+ * @param cs1 the first CharSequence, may be null
+ * @param cs2 the second CharSequence, may be 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(CharSequence cs1, CharSequence cs2) {
+ return cs1 == null ? cs2 == null : cs1.equals(cs2);
+ }
+
+ /**
+ * <p>Compares two CharSequences, returning {@code true} if they are equal ignoring
+ * the case.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered equal. Comparison is case insensitive.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence str1, CharSequence str2) {
+ if (str1 == null || str2 == null) {
+ return str1 == str2;
+ } else {
+ return CharSequenceUtils.regionMatches(str1, true, 0, str2, 0, Math.max(str1.length(), str2.length()));
+ }
+ }
+
+ // IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(int, int)} if possible.</p>
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code INDEX_NOT_FOUND (-1)}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf("", *) = -1
+ * StringUtils.indexOf("aabaabaa", 'a') = 0
+ * StringUtils.indexOf("aabaabaa", 'b') = 2
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @return the first index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, int) to indexOf(CharSequence, int)
+ */
+ public static int indexOf(CharSequence seq, int searchChar) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, 0);
+ }
+
+ /**
+ * <p>Finds the first index within a CharSequence from a start position,
+ * handling {@code null}.
+ * This method uses {@link String#indexOf(int, int)} if possible.</p>
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code (INDEX_NOT_FOUND) -1}.
+ * A negative start position is treated as zero.
+ * A start position greater than the string length returns {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf("", *, *) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', 0) = 2
+ * StringUtils.indexOf("aabaabaa", 'b', 3) = 5
+ * StringUtils.indexOf("aabaabaa", 'b', 9) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', -1) = 2
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, int, int) to indexOf(CharSequence, int, int)
+ */
+ public static int indexOf(CharSequence seq, int searchChar, int startPos) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, startPos);
+ }
+
+ /**
+ * <p>Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String, int)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf(*, null) = -1
+ * StringUtils.indexOf("", "") = 0
+ * StringUtils.indexOf("", *) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a") = 0
+ * StringUtils.indexOf("aabaabaa", "b") = 2
+ * StringUtils.indexOf("aabaabaa", "ab") = 1
+ * StringUtils.indexOf("aabaabaa", "") = 0
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence)
+ */
+ public static int indexOf(CharSequence seq, CharSequence searchSeq) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, 0);
+ }
+
+ /**
+ * <p>Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String, int)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf(*, null, *) = -1
+ * StringUtils.indexOf("", "", 0) = 0
+ * StringUtils.indexOf("", *, 0) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.indexOf("aabaabaa", "b", 0) = 2
+ * StringUtils.indexOf("aabaabaa", "ab", 0) = 1
+ * StringUtils.indexOf("aabaabaa", "b", 3) = 5
+ * StringUtils.indexOf("aabaabaa", "b", 9) = -1
+ * StringUtils.indexOf("aabaabaa", "b", -1) = 2
+ * StringUtils.indexOf("aabaabaa", "", 2) = 2
+ * StringUtils.indexOf("abc", "", 9) = 3
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, String, int) to indexOf(CharSequence, CharSequence, int)
+ */
+ public static int indexOf(CharSequence seq, CharSequence searchSeq, int startPos) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, startPos);
+ }
+
+ /**
+ * <p>Finds the n-th index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.ordinalIndexOf(null, *, *) = -1
+ * StringUtils.ordinalIndexOf(*, null, *) = -1
+ * StringUtils.ordinalIndexOf("", "", *) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 2) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 1) = 2
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 2) = 5
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 2) = 0
+ * </pre>
+ *
+ * <p>Note that 'head(CharSequence str, int n)' may be implemented as: </p>
+ *
+ * <pre>
+ * str.substring(0, lastOrdinalIndexOf(str, "\n", n))
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th {@code searchStr} to find
+ * @return the n-th index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ * @since 2.1
+ * @since 3.0 Changed signature from ordinalIndexOf(String, String, int) to ordinalIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int ordinalIndexOf(CharSequence str, CharSequence searchStr, int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, false);
+ }
+
+ /**
+ * <p>Finds the n-th index within a String, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th {@code searchStr} to find
+ * @param lastIndex true if lastOrdinalIndexOf() otherwise false if ordinalIndexOf()
+ * @return the n-th index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ */
+ // Shared code between ordinalIndexOf(String,String,int) and lastOrdinalIndexOf(String,String,int)
+ private static int ordinalIndexOf(CharSequence str, CharSequence searchStr, int ordinal, boolean lastIndex) {
+ if (str == null || searchStr == null || ordinal <= 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return lastIndex ? str.length() : 0;
+ }
+ int found = 0;
+ int index = lastIndex ? str.length() : INDEX_NOT_FOUND;
+ do {
+ if (lastIndex) {
+ index = CharSequenceUtils.lastIndexOf(str, searchStr, index - 1);
+ } else {
+ index = CharSequenceUtils.indexOf(str, searchStr, index + 1);
+ }
+ if (index < 0) {
+ return index;
+ }
+ found++;
+ } while (found < ordinal);
+ return index;
+ }
+
+ /**
+ * <p>Case in-sensitive find of the first index within a CharSequence.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfIgnoreCase(null, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null) = -1
+ * StringUtils.indexOfIgnoreCase("", "") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "a") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "b") = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "ab") = 1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from indexOfIgnoreCase(String, String) to indexOfIgnoreCase(CharSequence, CharSequence)
+ */
+ public static int indexOfIgnoreCase(CharSequence str, CharSequence searchStr) {
+ return indexOfIgnoreCase(str, searchStr, 0);
+ }
+
+ /**
+ * <p>Case in-sensitive find of the first index within a CharSequence
+ * from the specified position.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtils.indexOfIgnoreCase("abc", "", 9) = 3
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from indexOfIgnoreCase(String, String, int) to indexOfIgnoreCase(CharSequence, CharSequence, int)
+ */
+ public static int indexOfIgnoreCase(CharSequence str, CharSequence searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startPos < 0) {
+ startPos = 0;
+ }
+ int endLimit = (str.length() - searchStr.length()) + 1;
+ if (startPos > endLimit) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return startPos;
+ }
+ for (int i = startPos; i < endLimit; i++) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ // LastIndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Finds the last index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(int)} if possible.</p>
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf("", *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
+ * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @return the last index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, int) to lastIndexOf(CharSequence, int)
+ */
+ public static int lastIndexOf(CharSequence seq, int searchChar) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchChar, seq.length());
+ }
+
+ /**
+ * <p>Finds the last index within a CharSequence from a start position,
+ * handling {@code null}.
+ * This method uses {@link String#lastIndexOf(int, int)} if possible.</p>
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * A start position greater than the string length searches the whole string.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf("", *, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 4) = 2
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 0) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a', 0) = 0
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position
+ * @return the last index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, int, int) to lastIndexOf(CharSequence, int, int)
+ */
+ public static int lastIndexOf(CharSequence seq, int searchChar, int startPos) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchChar, startPos);
+ }
+
+ /**
+ * <p>Finds the last index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf(*, null) = -1
+ * StringUtils.lastIndexOf("", "") = 0
+ * StringUtils.lastIndexOf("aabaabaa", "a") = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b") = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab") = 4
+ * StringUtils.lastIndexOf("aabaabaa", "") = 8
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return the last index of the search String,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, String) to lastIndexOf(CharSequence, CharSequence)
+ */
+ public static int lastIndexOf(CharSequence seq, CharSequence searchSeq) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchSeq, seq.length());
+ }
+
+ /**
+ * <p>Finds the n-th last index within a String, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String)}.</p>
+ *
+ * <p>A {@code null} String will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.lastOrdinalIndexOf(null, *, *) = -1
+ * StringUtils.lastOrdinalIndexOf(*, null, *) = -1
+ * StringUtils.lastOrdinalIndexOf("", "", *) = 0
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1) = 7
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2) = 6
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1) = 5
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2) = 2
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) = 4
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) = 1
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1) = 8
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2) = 8
+ * </pre>
+ *
+ * <p>Note that 'tail(CharSequence str, int n)' may be implemented as: </p>
+ *
+ * <pre>
+ * str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th last {@code searchStr} to find
+ * @return the n-th last index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastOrdinalIndexOf(String, String, int) to lastOrdinalIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int lastOrdinalIndexOf(CharSequence str, CharSequence searchStr, int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, true);
+ }
+
+ /**
+ * <p>Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String, int)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf(*, null, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 8) = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b", 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab", 8) = 4
+ * StringUtils.lastIndexOf("aabaabaa", "b", 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "b", -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.lastIndexOf("aabaabaa", "b", 0) = -1
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, String, int) to lastIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int lastIndexOf(CharSequence seq, CharSequence searchSeq, int startPos) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos);
+ }
+
+ /**
+ * <p>Case in-sensitive find of the last index within a CharSequence.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfIgnoreCase(null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A") = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B") = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String) to lastIndexOfIgnoreCase(CharSequence, CharSequence)
+ */
+ public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return lastIndexOfIgnoreCase(str, searchStr, str.length());
+ }
+
+ /**
+ * <p>Case in-sensitive find of the last index within a CharSequence
+ * from the specified position.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8) = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0) = -1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param startPos the start position
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String, int) to lastIndexOfIgnoreCase(CharSequence, CharSequence, int)
+ */
+ public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startPos > (str.length() - searchStr.length())) {
+ startPos = str.length() - searchStr.length();
+ }
+ if (startPos < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return startPos;
+ }
+
+ for (int i = startPos; i >= 0; i--) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ // Contains
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if CharSequence contains a search character, handling {@code null}.
+ * This method uses {@link String#indexOf(int)} if possible.</p>
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains("", *) = false
+ * StringUtils.contains("abc", 'a') = true
+ * StringUtils.contains("abc", 'z') = false
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @return true if the CharSequence contains the search character,
+ * false if not or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int)
+ */
+ public static boolean contains(CharSequence seq, int searchChar) {
+ if (isEmpty(seq)) {
+ return false;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, 0) >= 0;
+ }
+
+ /**
+ * <p>Checks if CharSequence contains a search CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains(*, null) = false
+ * StringUtils.contains("", "") = true
+ * StringUtils.contains("abc", "") = true
+ * StringUtils.contains("abc", "a") = true
+ * StringUtils.contains("abc", "z") = false
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return true if the CharSequence contains the search CharSequence,
+ * false if not or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from contains(String, String) to contains(CharSequence, CharSequence)
+ */
+ public static boolean contains(CharSequence seq, CharSequence searchSeq) {
+ if (seq == null || searchSeq == null) {
+ return false;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0;
+ }
+
+ /**
+ * <p>Checks if CharSequence contains a search CharSequence irrespective of case,
+ * handling {@code null}. Case-insensitivity is defined as by
+ * {@link String#equalsIgnoreCase(String)}.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains(*, null) = false
+ * StringUtils.contains("", "") = true
+ * StringUtils.contains("abc", "") = true
+ * StringUtils.contains("abc", "a") = true
+ * StringUtils.contains("abc", "z") = false
+ * StringUtils.contains("abc", "A") = true
+ * StringUtils.contains("abc", "Z") = false
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return true if the CharSequence contains the search CharSequence irrespective of
+ * case or false if not or {@code null} string input
+ * @since 3.0 Changed signature from containsIgnoreCase(String, String) to containsIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean containsIgnoreCase(CharSequence str, CharSequence searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ int len = searchStr.length();
+ int max = str.length() - len;
+ for (int i = 0; i <= max; i++) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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 java.lang.Character#isWhitespace
+ * @since 3.0
+ */
+ // From org.springframework.util.StringUtils, under Apache License 2.0
+ public static boolean containsWhitespace(CharSequence seq) {
+ if (isEmpty(seq)) {
+ return false;
+ }
+ int strLen = seq.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(seq.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // IndexOfAny chars
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Search a CharSequence to find the first index of any
+ * character in the given set of characters.</p>
+ *
+ * <p>A {@code null} String will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny("", *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, []) = -1
+ * StringUtils.indexOfAny("zzabyycdxx",['z','a']) = 0
+ * StringUtils.indexOfAny("zzabyycdxx",['b','y']) = 3
+ * StringUtils.indexOfAny("aba", ['z']) = -1
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...)
+ */
+ public static int indexOfAny(CharSequence cs, char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ int csLen = cs.length();
+ int csLast = csLen - 1;
+ int searchLen = searchChars.length;
+ int searchLast = searchLen - 1;
+ for (int i = 0; i < csLen; i++) {
+ char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (i < csLast && j < searchLast && Character.isHighSurrogate(ch)) {
+ // ch is a supplementary character
+ if (searchChars[j + 1] == cs.charAt(i + 1)) {
+ return i;
+ }
+ } else {
+ return i;
+ }
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Search a CharSequence to find the first index of any
+ * character in the given set of characters.</p>
+ *
+ * <p>A {@code null} String will return {@code -1}.
+ * A {@code null} search string will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny("", *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, "") = -1
+ * StringUtils.indexOfAny("zzabyycdxx", "za") = 0
+ * StringUtils.indexOfAny("zzabyycdxx", "by") = 3
+ * StringUtils.indexOfAny("aba","z") = -1
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, String)
+ */
+ public static int indexOfAny(CharSequence cs, String searchChars) {
+ if (isEmpty(cs) || isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ return indexOfAny(cs, searchChars.toCharArray());
+ }
+
+ // ContainsAny
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if the CharSequence contains any character in the given
+ * set of characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} or zero length search array will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, []) = false
+ * StringUtils.containsAny("zzabyycdxx",['z','a']) = true
+ * StringUtils.containsAny("zzabyycdxx",['b','y']) = true
+ * StringUtils.containsAny("aba", ['z']) = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the {@code true} if any of the chars are found,
+ * {@code false} if no match or null input
+ * @since 2.4
+ * @since 3.0 Changed signature from containsAny(String, char[]) to containsAny(CharSequence, char...)
+ */
+ public static boolean containsAny(CharSequence cs, char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return false;
+ }
+ int csLength = cs.length();
+ int searchLength = searchChars.length;
+ int csLast = csLength - 1;
+ int searchLast = searchLength - 1;
+ for (int i = 0; i < csLength; i++) {
+ char ch = cs.charAt(i);
+ for (int j = 0; j < searchLength; j++) {
+ if (searchChars[j] == ch) {
+ if (Character.isHighSurrogate(ch)) {
+ if (j == searchLast) {
+ // missing low surrogate, fine, like String.indexOf(String)
+ return true;
+ }
+ if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
+ return true;
+ }
+ } else {
+ // ch is in the Basic Multilingual Plane
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * <p>
+ * Checks if the CharSequence contains any character in the given set of characters.
+ * </p>
+ *
+ * <p>
+ * A {@code null} CharSequence will return {@code false}. A {@code null} search CharSequence will return
+ * {@code false}.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, "") = false
+ * StringUtils.containsAny("zzabyycdxx", "za") = true
+ * StringUtils.containsAny("zzabyycdxx", "by") = true
+ * StringUtils.containsAny("aba","z") = false
+ * </pre>
+ *
+ * @param cs
+ * the CharSequence to check, may be null
+ * @param searchChars
+ * the chars to search for, may be null
+ * @return the {@code true} if any of the chars are found, {@code false} if no match or null input
+ * @since 2.4
+ * @since 3.0 Changed signature from containsAny(String, String) to containsAny(CharSequence, CharSequence)
+ */
+ public static boolean containsAny(CharSequence cs, CharSequence searchChars) {
+ if (searchChars == null) {
+ return false;
+ }
+ return containsAny(cs, CharSequenceUtils.toCharArray(searchChars));
+ }
+
+ // IndexOfAnyBut chars
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Searches a CharSequence to find the first index of any
+ * character not in the given set of characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAnyBut(null, *) = -1
+ * StringUtils.indexOfAnyBut("", *) = -1
+ * StringUtils.indexOfAnyBut(*, null) = -1
+ * StringUtils.indexOfAnyBut(*, []) = -1
+ * StringUtils.indexOfAnyBut("zzabyycdxx", new char[] {'z', 'a'} ) = 3
+ * StringUtils.indexOfAnyBut("aba", new char[] {'z'} ) = 0
+ * StringUtils.indexOfAnyBut("aba", new char[] {'a', 'b'} ) = -1
+
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAnyBut(String, char[]) to indexOfAnyBut(CharSequence, char...)
+ */
+ public static int indexOfAnyBut(CharSequence cs, char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ int csLen = cs.length();
+ int csLast = csLen - 1;
+ int searchLen = searchChars.length;
+ int searchLast = searchLen - 1;
+ outer:
+ for (int i = 0; i < csLen; i++) {
+ char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (i < csLast && j < searchLast && Character.isHighSurrogate(ch)) {
+ if (searchChars[j + 1] == cs.charAt(i + 1)) {
+ continue outer;
+ }
+ } else {
+ continue outer;
+ }
+ }
+ }
+ return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Search a CharSequence to find the first index of any
+ * character not in the given set of characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or empty search string will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAnyBut(null, *) = -1
+ * StringUtils.indexOfAnyBut("", *) = -1
+ * StringUtils.indexOfAnyBut(*, null) = -1
+ * StringUtils.indexOfAnyBut(*, "") = -1
+ * StringUtils.indexOfAnyBut("zzabyycdxx", "za") = 3
+ * StringUtils.indexOfAnyBut("zzabyycdxx", "") = -1
+ * StringUtils.indexOfAnyBut("aba","ab") = -1
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAnyBut(String, String) to indexOfAnyBut(CharSequence, CharSequence)
+ */
+ public static int indexOfAnyBut(CharSequence seq, CharSequence searchChars) {
+ if (isEmpty(seq) || isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ int strLen = seq.length();
+ for (int i = 0; i < strLen; i++) {
+ char ch = seq.charAt(i);
+ boolean chFound = CharSequenceUtils.indexOf(searchChars, ch, 0) >= 0;
+ if (i + 1 < strLen && Character.isHighSurrogate(ch)) {
+ char ch2 = seq.charAt(i + 1);
+ if (chFound && CharSequenceUtils.indexOf(searchChars, ch2, 0) < 0) {
+ return i;
+ }
+ } else {
+ if (!chFound) {
+ return i;
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ // ContainsOnly
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if the CharSequence contains only certain characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} valid character array will return {@code false}.
+ * An empty CharSequence (length()=0) always returns {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.containsOnly(null, *) = false
+ * StringUtils.containsOnly(*, null) = false
+ * StringUtils.containsOnly("", *) = true
+ * StringUtils.containsOnly("ab", '') = false
+ * StringUtils.containsOnly("abab", 'abc') = true
+ * StringUtils.containsOnly("ab1", 'abc') = false
+ * StringUtils.containsOnly("abz", 'abc') = false
+ * </pre>
+ *
+ * @param cs the String to check, may be null
+ * @param valid an array of valid chars, may be null
+ * @return true if it only contains valid chars and is non-null
+ * @since 3.0 Changed signature from containsOnly(String, char[]) to containsOnly(CharSequence, char...)
+ */
+ public static boolean containsOnly(CharSequence cs, char... valid) {
+ // All these pre-checks are to maintain API with an older version
+ if (valid == null || cs == null) {
+ return false;
+ }
+ if (cs.length() == 0) {
+ return true;
+ }
+ if (valid.length == 0) {
+ return false;
+ }
+ return indexOfAnyBut(cs, valid) == INDEX_NOT_FOUND;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only certain characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} valid character String will return {@code false}.
+ * An empty String (length()=0) always returns {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.containsOnly(null, *) = false
+ * StringUtils.containsOnly(*, null) = false
+ * StringUtils.containsOnly("", *) = true
+ * StringUtils.containsOnly("ab", "") = false
+ * StringUtils.containsOnly("abab", "abc") = true
+ * StringUtils.containsOnly("ab1", "abc") = false
+ * StringUtils.containsOnly("abz", "abc") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param validChars a String of valid chars, may be null
+ * @return true if it only contains valid chars and is non-null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, String)
+ */
+ public static boolean containsOnly(CharSequence cs, String validChars) {
+ if (cs == null || validChars == null) {
+ return false;
+ }
+ return containsOnly(cs, validChars.toCharArray());
+ }
+
+ // ContainsNone
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks that the CharSequence does not contain certain characters.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs, char... searchChars) {
+ if (cs == null || searchChars == null) {
+ return true;
+ }
+ int csLen = cs.length();
+ int csLast = csLen - 1;
+ int searchLen = searchChars.length;
+ int searchLast = searchLen - 1;
+ for (int i = 0; i < csLen; i++) {
+ 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;
+ }
+
+ /**
+ * <p>Checks that the CharSequence does not contain certain characters.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code true}.
+ * A {@code null} invalid character array will return {@code true}.
+ * An empty String ("") always returns true.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs, String invalidChars) {
+ if (cs == null || invalidChars == null) {
+ return true;
+ }
+ return containsNone(cs, invalidChars.toCharArray());
+ }
+
+ // IndexOfAny strings
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Find the first index of any of a set of potential substrings.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.
+ * A {@code null} search array entry will be ignored, but a search
+ * array containing "" will return {@code 0} if {@code str} is not
+ * null. This method uses {@link String#indexOf(String)} if possible.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, []) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["ab","cd"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["cd","ab"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["zab","aby"]) = 1
+ * StringUtils.indexOfAny("zzabyycdxx", [""]) = 0
+ * StringUtils.indexOfAny("", [""]) = 0
+ * StringUtils.indexOfAny("", ["a"]) = -1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStrs the CharSequences to search for, may be null
+ * @return the first index of any of the searchStrs in str, -1 if no match
+ * @since 3.0 Changed signature from indexOfAny(String, String[]) to indexOfAny(CharSequence, CharSequence...)
+ */
+ public static int indexOfAny(CharSequence str, CharSequence... searchStrs) {
+ if (str == null || searchStrs == null) {
+ return INDEX_NOT_FOUND;
+ }
+ int sz = searchStrs.length;
+
+ // String's can't have a MAX_VALUEth index.
+ int ret = Integer.MAX_VALUE;
+
+ int tmp = 0;
+ for (int i = 0; i < sz; i++) {
+ CharSequence search = searchStrs[i];
+ if (search == null) {
+ continue;
+ }
+ tmp = CharSequenceUtils.indexOf(str, search, 0);
+ if (tmp == INDEX_NOT_FOUND) {
+ continue;
+ }
+
+ if (tmp < ret) {
+ ret = tmp;
+ }
+ }
+
+ return (ret == Integer.MAX_VALUE) ? INDEX_NOT_FOUND : ret;
+ }
+
+ /**
+ * <p>Find the latest index of any of a set of potential substrings.</p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} search array will return {@code -1}.
+ * A {@code null} or zero length search array entry will be ignored,
+ * but a search array containing "" will return the length of {@code str}
+ * if {@code str} is not null. This method uses {@link String#indexOf(String)} if possible</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfAny(null, *) = -1
+ * StringUtils.lastIndexOfAny(*, null) = -1
+ * StringUtils.lastIndexOfAny(*, []) = -1
+ * StringUtils.lastIndexOfAny(*, [null]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["ab","cd"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["cd","ab"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn",""]) = 10
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStrs the CharSequences to search for, may be null
+ * @return the last index of any of the CharSequences, -1 if no match
+ * @since 3.0 Changed signature from lastIndexOfAny(String, String[]) to lastIndexOfAny(CharSequence, CharSequence)
+ */
+ public static int lastIndexOfAny(CharSequence str, CharSequence... searchStrs) {
+ if (str == null || searchStrs == null) {
+ return INDEX_NOT_FOUND;
+ }
+ int sz = searchStrs.length;
+ int ret = INDEX_NOT_FOUND;
+ int tmp = 0;
+ for (int i = 0; i < sz; i++) {
+ CharSequence search = searchStrs[i];
+ if (search == null) {
+ continue;
+ }
+ tmp = CharSequenceUtils.lastIndexOf(str, search, str.length());
+ if (tmp > ret) {
+ ret = tmp;
+ }
+ }
+ return ret;
+ }
+
+ // Substring
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a substring from the specified String avoiding exceptions.</p>
+ *
+ * <p>A negative start position can be used to start {@code n}
+ * characters from the end of the String.</p>
+ *
+ * <p>A {@code null} String will return {@code null}.
+ * An empty ("") String will return "".</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(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);
+ }
+
+ /**
+ * <p>Gets a substring from the specified String avoiding exceptions.</p>
+ *
+ * <p>A negative start position can be used to start/end {@code n}
+ * characters from the end of the String.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>If {@code start} is not strictly to the left of {@code end}, ""
+ * is returned.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the leftmost {@code len} characters of a String.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.left(null, *) = null
+ * StringUtils.left(*, -ve) = ""
+ * StringUtils.left("", *) = ""
+ * StringUtils.left("abc", 0) = ""
+ * StringUtils.left("abc", 2) = "ab"
+ * StringUtils.left("abc", 4) = "abc"
+ * </pre>
+ *
+ * @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(String str, int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(0, len);
+ }
+
+ /**
+ * <p>Gets the rightmost {@code len} characters of a String.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.right(null, *) = null
+ * StringUtils.right(*, -ve) = ""
+ * StringUtils.right("", *) = ""
+ * StringUtils.right("abc", 0) = ""
+ * StringUtils.right("abc", 2) = "bc"
+ * StringUtils.right("abc", 4) = "abc"
+ * </pre>
+ *
+ * @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(String str, int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(str.length() - len);
+ }
+
+ /**
+ * <p>Gets {@code len} characters from the middle of a String.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int pos, 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the substring before the first occurrence of a separator.
+ * The separator is not returned.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>If nothing is found, the string input is returned.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String separator) {
+ if (isEmpty(str) || separator == null) {
+ return str;
+ }
+ if (separator.length() == 0) {
+ return EMPTY;
+ }
+ int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * <p>Gets the substring after the first occurrence of a separator.
+ * The separator is not returned.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (separator == null) {
+ return EMPTY;
+ }
+ int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * <p>Gets the substring before the last occurrence of a separator.
+ * The separator is not returned.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>If nothing is found, the string input is returned.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String separator) {
+ if (isEmpty(str) || isEmpty(separator)) {
+ return str;
+ }
+ int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * <p>Gets the substring after the last occurrence of a separator.
+ * The separator is not returned.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * 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") = ""
+ * </pre>
+ *
+ * @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(String str, String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (isEmpty(separator)) {
+ return EMPTY;
+ }
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the String that is nested in between two instances of the
+ * same String.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} tag returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.substringBetween(null, *) = null
+ * StringUtils.substringBetween("", "") = ""
+ * StringUtils.substringBetween("", "tag") = null
+ * StringUtils.substringBetween("tagabctag", null) = null
+ * StringUtils.substringBetween("tagabctag", "") = ""
+ * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+ * </pre>
+ *
+ * @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(String str, String tag) {
+ return substringBetween(str, tag, tag);
+ }
+
+ /**
+ * <p>Gets the String that is nested in between two Strings.
+ * Only the first match is returned.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String open, String close) {
+ if (str == null || open == null || close == null) {
+ return null;
+ }
+ int start = str.indexOf(open);
+ if (start != INDEX_NOT_FOUND) {
+ int end = str.indexOf(close, start + open.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str.substring(start + open.length(), end);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * <p>Searches a String for substrings delimited by a start and end tag,
+ * returning all matching substrings in an array.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} open/close returns {@code null} (no match).
+ * An empty ("") open/close returns {@code null} (no match).</p>
+ *
+ * <pre>
+ * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
+ * StringUtils.substringsBetween(null, *, *) = null
+ * StringUtils.substringsBetween(*, null, *) = null
+ * StringUtils.substringsBetween(*, *, null) = null
+ * StringUtils.substringsBetween("", "[", "]") = []
+ * </pre>
+ *
+ * @param str the String containing the substrings, null returns null, empty returns empty
+ * @param open the String identifying the start of the substring, empty returns null
+ * @param close the String identifying the end of the substring, empty returns null
+ * @return a String Array of substrings, or {@code null} if no match
+ * @since 2.3
+ */
+ public static String[] substringsBetween(String str, String open, String close) {
+ if (str == null || isEmpty(open) || isEmpty(close)) {
+ return null;
+ }
+ int strLen = str.length();
+ if (strLen == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ int closeLen = close.length();
+ int openLen = open.length();
+ List<String> list = new ArrayList<String>();
+ int pos = 0;
+ while (pos < (strLen - closeLen)) {
+ int start = str.indexOf(open, pos);
+ if (start < 0) {
+ break;
+ }
+ start += openLen;
+ int end = str.indexOf(close, start);
+ if (end < 0) {
+ break;
+ }
+ list.add(str.substring(start, end));
+ pos = end + closeLen;
+ }
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.toArray(new String [list.size()]);
+ }
+
+ // Nested extraction
+ //-----------------------------------------------------------------------
+
+ // Splitting
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Splits the provided text into an array, using whitespace as the
+ * separator.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.split(null) = null
+ * StringUtils.split("") = []
+ * StringUtils.split("abc def") = ["abc", "def"]
+ * StringUtils.split("abc def") = ["abc", "def"]
+ * StringUtils.split(" abc ") = ["abc"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(String str) {
+ return split(str, null, -1);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator specified.
+ * This is an alternative to using StringTokenizer.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *) = null
+ * StringUtils.split("", *) = []
+ * StringUtils.split("a.b.c", '.') = ["a", "b", "c"]
+ * StringUtils.split("a..b.c", '.') = ["a", "b", "c"]
+ * StringUtils.split("a:b:c", '.') = ["a:b:c"]
+ * StringUtils.split("a b c", ' ') = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChar the character used as the delimiter
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String[] split(String str, char separatorChar) {
+ return splitWorker(str, separatorChar, false);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separators specified.
+ * This is an alternative to using StringTokenizer.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *) = null
+ * StringUtils.split("", *) = []
+ * StringUtils.split("abc def", null) = ["abc", "def"]
+ * StringUtils.split("abc def", " ") = ["abc", "def"]
+ * StringUtils.split("abc def", " ") = ["abc", "def"]
+ * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(String str, String separatorChars) {
+ return splitWorker(str, separatorChars, -1, false);
+ }
+
+ /**
+ * <p>Splits the provided text into an array with a maximum length,
+ * separators specified.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <p>If more than {@code max} delimited substrings are found, the last
+ * returned string includes all characters after the first {@code max - 1}
+ * returned strings (including separator characters).</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *, *) = null
+ * StringUtils.split("", *, *) = []
+ * StringUtils.split("ab de fg", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab de fg", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab:cd:ef", ":", 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(String str, String separatorChars, int max) {
+ return splitWorker(str, separatorChars, max, false);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator string specified.</p>
+ *
+ * <p>The separator(s) will not be included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparator(null, *) = null
+ * StringUtils.splitByWholeSeparator("", *) = []
+ * StringUtils.splitByWholeSeparator("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String was input
+ */
+ public static String[] splitByWholeSeparator(String str, String separator) {
+ return splitByWholeSeparatorWorker( str, separator, -1, false ) ;
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator string specified.
+ * Returns a maximum of {@code max} substrings.</p>
+ *
+ * <p>The separator(s) will not be included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparator(null, *, *) = null
+ * StringUtils.splitByWholeSeparator("", *, *) = []
+ * StringUtils.splitByWholeSeparator("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @return an array of parsed Strings, {@code null} if null String was input
+ */
+ public static String[] splitByWholeSeparator( String str, String separator, int max ) {
+ return splitByWholeSeparatorWorker(str, separator, max, false);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator string specified. </p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *) = null
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *) = []
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null) = ["ab", "", "", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String was input
+ * @since 2.4
+ */
+ public static String[] splitByWholeSeparatorPreserveAllTokens(String str, String separator) {
+ return splitByWholeSeparatorWorker(str, separator, -1, true);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator string specified.
+ * Returns a maximum of {@code max} substrings.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *, *) = null
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *, *) = []
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0) = ["ab", "", "", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @return an array of parsed Strings, {@code null} if null String was input
+ * @since 2.4
+ */
+ public static String[] splitByWholeSeparatorPreserveAllTokens(String str, String separator, int max) {
+ return splitByWholeSeparatorWorker(str, separator, max, true);
+ }
+
+ /**
+ * Performs the logic for the {@code splitByWholeSeparatorPreserveAllTokens} methods.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ private static String[] splitByWholeSeparatorWorker(
+ String str, String separator, int max, boolean preserveAllTokens) {
+ if (str == null) {
+ return null;
+ }
+
+ int len = str.length();
+
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+
+ if ((separator == null) || (EMPTY.equals(separator))) {
+ // Split on whitespace.
+ return splitWorker(str, null, max, preserveAllTokens);
+ }
+
+ int separatorLength = separator.length();
+
+ ArrayList<String> substrings = new ArrayList<String>();
+ int numberOfSubstrings = 0;
+ int beg = 0;
+ int end = 0;
+ while (end < len) {
+ end = str.indexOf(separator, beg);
+
+ if (end > -1) {
+ if (end > beg) {
+ numberOfSubstrings += 1;
+
+ if (numberOfSubstrings == max) {
+ end = len;
+ substrings.add(str.substring(beg));
+ } else {
+ // The following is OK, because String.substring( beg, end ) excludes
+ // the character at the position 'end'.
+ substrings.add(str.substring(beg, end));
+
+ // Set the starting point for the next search.
+ // The following is equivalent to beg = end + (separatorLength - 1) + 1,
+ // which is the right calculation:
+ beg = end + separatorLength;
+ }
+ } else {
+ // We found a consecutive occurrence of the separator, so skip it.
+ if (preserveAllTokens) {
+ numberOfSubstrings += 1;
+ if (numberOfSubstrings == max) {
+ end = len;
+ substrings.add(str.substring(beg));
+ } else {
+ substrings.add(EMPTY);
+ }
+ }
+ beg = end + separatorLength;
+ }
+ } else {
+ // String.substring( beg ) goes from 'beg' to the end of the String.
+ substrings.add(str.substring(beg));
+ end = len;
+ }
+ }
+
+ return substrings.toArray(new String[substrings.size()]);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * <p>Splits the provided text into an array, using whitespace as the
+ * separator, preserving all tokens, including empty tokens created by
+ * adjacent separators. This is an alternative to using StringTokenizer.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null) = null
+ * StringUtils.splitPreserveAllTokens("") = []
+ * StringUtils.splitPreserveAllTokens("abc def") = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def") = ["abc", "", "def"]
+ * StringUtils.splitPreserveAllTokens(" abc ") = ["", "abc", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(String str) {
+ return splitWorker(str, null, -1, true);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator specified,
+ * preserving all tokens, including empty tokens created by adjacent
+ * separators. This is an alternative to using StringTokenizer.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *) = null
+ * StringUtils.splitPreserveAllTokens("", *) = []
+ * StringUtils.splitPreserveAllTokens("a.b.c", '.') = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a..b.c", '.') = ["a", "", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a:b:c", '.') = ["a:b:c"]
+ * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a b c", ' ') = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a b c ", ' ') = ["a", "b", "c", ""]
+ * StringUtils.splitPreserveAllTokens("a b c ", ' ') = ["a", "b", "c", "", ""]
+ * StringUtils.splitPreserveAllTokens(" a b c", ' ') = ["", a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens(" a b c", ' ') = ["", "", a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens(" a b c ", ' ') = ["", a", "b", "c", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChar the character used as the delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(String str, char separatorChar) {
+ return splitWorker(str, separatorChar, true);
+ }
+
+ /**
+ * Performs the logic for the {@code split} and
+ * {@code splitPreserveAllTokens} methods that do not return a
+ * maximum array length.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChar the separate character
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
+ // Performance tuned for 2.0 (JDK1.4)
+
+ if (str == null) {
+ return null;
+ }
+ int len = str.length();
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ List<String> list = new ArrayList<String>();
+ int i = 0, start = 0;
+ boolean match = false;
+ boolean lastMatch = false;
+ while (i < len) {
+ if (str.charAt(i) == separatorChar) {
+ if (match || preserveAllTokens) {
+ list.add(str.substring(start, i));
+ match = false;
+ lastMatch = true;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ if (match || (preserveAllTokens && lastMatch)) {
+ list.add(str.substring(start, i));
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separators specified,
+ * preserving all tokens, including empty tokens created by adjacent
+ * separators. This is an alternative to using StringTokenizer.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *) = null
+ * StringUtils.splitPreserveAllTokens("", *) = []
+ * StringUtils.splitPreserveAllTokens("abc def", null) = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "", def"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":") = ["ab", "cd", "ef", ""]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
+ * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":") = ["ab", "", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens(":cd:ef", ":") = ["", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("::cd:ef", ":") = ["", "", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens(":cd:ef:", ":") = ["", cd", "ef", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(String str, String separatorChars) {
+ return splitWorker(str, separatorChars, -1, true);
+ }
+
+ /**
+ * <p>Splits the provided text into an array with a maximum length,
+ * separators specified, preserving all tokens, including empty tokens
+ * created by adjacent separators.</p>
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <p>If more than {@code max} delimited substrings are found, the last
+ * returned string includes all characters after the first {@code max - 1}
+ * returned strings (including separator characters).</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *, *) = null
+ * StringUtils.splitPreserveAllTokens("", *, *) = []
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0) = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 2) = ["ab", " de fg"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 3) = ["ab", "", " de fg"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 4) = ["ab", "", "", "de fg"]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(String str, String separatorChars, int max) {
+ return splitWorker(str, separatorChars, max, true);
+ }
+
+ /**
+ * Performs the logic for the {@code split} and
+ * {@code splitPreserveAllTokens} methods that return a maximum array
+ * length.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the separate character
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit.
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) {
+ // Performance tuned for 2.0 (JDK1.4)
+ // Direct code is quicker than StringTokenizer.
+ // Also, StringTokenizer uses isSpace() not isWhitespace()
+
+ if (str == null) {
+ return null;
+ }
+ int len = str.length();
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ List<String> list = new ArrayList<String>();
+ int sizePlus1 = 1;
+ int i = 0, start = 0;
+ boolean match = false;
+ boolean lastMatch = false;
+ if (separatorChars == null) {
+ // Null separator means use whitespace
+ while (i < len) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else if (separatorChars.length() == 1) {
+ // Optimise 1 character case
+ char sep = separatorChars.charAt(0);
+ while (i < len) {
+ if (str.charAt(i) == sep) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else {
+ // standard case
+ while (i < len) {
+ if (separatorChars.indexOf(str.charAt(i)) >= 0) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ }
+ if (match || (preserveAllTokens && lastMatch)) {
+ list.add(str.substring(start, i));
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * <p>Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens.
+ * <pre>
+ * StringUtils.splitByCharacterType(null) = null
+ * StringUtils.splitByCharacterType("") = []
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterType("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterType("fooBar") = ["foo", "B", "ar"]
+ * StringUtils.splitByCharacterType("foo200Bar") = ["foo", "200", "B", "ar"]
+ * StringUtils.splitByCharacterType("ASFRules") = ["ASFR", "ules"]
+ * </pre>
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterType(String str) {
+ return splitByCharacterType(str, false);
+ }
+
+ /**
+ * <p>Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: the character of type
+ * {@code Character.UPPERCASE_LETTER}, if any, immediately
+ * preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ * <pre>
+ * StringUtils.splitByCharacterTypeCamelCase(null) = null
+ * StringUtils.splitByCharacterTypeCamelCase("") = []
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterTypeCamelCase("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterTypeCamelCase("fooBar") = ["foo", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("foo200Bar") = ["foo", "200", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("ASFRules") = ["ASF", "Rules"]
+ * </pre>
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterTypeCamelCase(String str) {
+ return splitByCharacterType(str, true);
+ }
+
+ /**
+ * <p>Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: if {@code camelCase} is {@code true},
+ * the character of type {@code Character.UPPERCASE_LETTER}, if any,
+ * immediately preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ * @param str the String to split, may be {@code null}
+ * @param camelCase whether to use so-called "camel-case" for letter types
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ private static String[] splitByCharacterType(String str, boolean camelCase) {
+ if (str == null) {
+ return null;
+ }
+ if (str.length() == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ char[] c = str.toCharArray();
+ List<String> list = new ArrayList<String>();
+ int tokenStart = 0;
+ int currentType = Character.getType(c[tokenStart]);
+ for (int pos = tokenStart + 1; pos < c.length; pos++) {
+ int type = Character.getType(c[pos]);
+ if (type == currentType) {
+ continue;
+ }
+ if (camelCase && type == Character.LOWERCASE_LETTER && currentType == Character.UPPERCASE_LETTER) {
+ int newTokenStart = pos - 1;
+ if (newTokenStart != tokenStart) {
+ list.add(new String(c, tokenStart, newTokenStart - tokenStart));
+ tokenStart = newTokenStart;
+ }
+ } else {
+ list.add(new String(c, tokenStart, pos - tokenStart));
+ tokenStart = pos;
+ }
+ currentType = type;
+ }
+ list.add(new String(c, tokenStart, c.length - tokenStart));
+ return list.toArray(new String[list.size()]);
+ }
+
+ // Joining
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Joins the elements of the provided array into a single String
+ * containing the provided list of elements.</p>
+ *
+ * <p>No separator is added to the joined String.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null) = null
+ * StringUtils.join([]) = ""
+ * StringUtils.join([null]) = ""
+ * StringUtils.join(["a", "b", "c"]) = "abc"
+ * StringUtils.join([null, "", "a"]) = "a"
+ * </pre>
+ *
+ * @param <T> the specific type of values to join together
+ * @param elements the values to join together, may be null
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ * @since 3.0 Changed signature to use varargs
+ */
+ public static <T> String join(T... elements) {
+ return join(elements, null);
+ }
+
+ /**
+ * <p>Joins the elements of the provided array into a single String
+ * containing the provided list of elements.</p>
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * 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([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @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 2.0
+ */
+ public static String join(Object[] array, char separator) {
+ if (array == null) {
+ return null;
+ }
+
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * <p>Joins the elements of the provided array into a single String
+ * containing the provided list of elements.</p>
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * 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([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @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 2.0
+ */
+ public static String join(Object[] array, char separator, int startIndex, int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ int noOfItems = (endIndex - startIndex);
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+
+ 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();
+ }
+
+ /**
+ * <p>Joins the elements of the provided array into a single String
+ * containing the provided list of elements.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(Object[] array, String separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * <p>Joins the elements of the provided array into a single String
+ * containing the provided list of elements.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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. 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
+ */
+ public static String join(Object[] array, String separator, int startIndex, 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)
+ int noOfItems = (endIndex - startIndex);
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+
+ 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();
+ }
+
+ /**
+ * <p>Joins the elements of the provided {@code Iterator} into
+ * a single String containing the provided elements.</p>
+ *
+ * <p>No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],char)}. </p>
+ *
+ * @param iterator the {@code Iterator} of values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.0
+ */
+ public static String join(Iterator<?> iterator, char separator) {
+
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ Object first = iterator.next();
+ if (!iterator.hasNext()) {
+ return ObjectUtils.toString(first);
+ }
+
+ // two or more elements
+ StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+ if (first != null) {
+ buf.append(first);
+ }
+
+ while (iterator.hasNext()) {
+ buf.append(separator);
+ Object obj = iterator.next();
+ if (obj != null) {
+ buf.append(obj);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * <p>Joins the elements of the provided {@code Iterator} into
+ * a single String containing the provided elements.</p>
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],String)}. </p>
+ *
+ * @param iterator the {@code Iterator} 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 iterator input
+ */
+ public static String join(Iterator<?> iterator, String separator) {
+
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ Object first = iterator.next();
+ if (!iterator.hasNext()) {
+ return ObjectUtils.toString(first);
+ }
+
+ // two or more elements
+ StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+ if (first != null) {
+ buf.append(first);
+ }
+
+ while (iterator.hasNext()) {
+ if (separator != null) {
+ buf.append(separator);
+ }
+ Object obj = iterator.next();
+ if (obj != null) {
+ buf.append(obj);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * <p>Joins the elements of the provided {@code Iterable} into
+ * a single String containing the provided elements.</p>
+ *
+ * <p>No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],char)}. </p>
+ *
+ * @param iterable the {@code Iterable} providing the values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.3
+ */
+ public static String join(Iterable<?> iterable, char separator) {
+ if (iterable == null) {
+ return null;
+ }
+ return join(iterable.iterator(), separator);
+ }
+
+ /**
+ * <p>Joins the elements of the provided {@code Iterable} into
+ * a single String containing the provided elements.</p>
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],String)}. </p>
+ *
+ * @param iterable the {@code Iterable} providing the 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 iterator input
+ * @since 2.3
+ */
+ public static String join(Iterable<?> iterable, String separator) {
+ if (iterable == null) {
+ return null;
+ }
+ return join(iterable.iterator(), separator);
+ }
+
+ // Delete
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Deletes all whitespaces from a String as defined by
+ * {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.deleteWhitespace(null) = null
+ * StringUtils.deleteWhitespace("") = ""
+ * StringUtils.deleteWhitespace("abc") = "abc"
+ * StringUtils.deleteWhitespace(" ab c ") = "abc"
+ * </pre>
+ *
+ * @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(String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ int sz = str.length();
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Removes a substring only if it is at the beginning of a source string,
+ * otherwise returns the source string.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.startsWith(remove)){
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * <p>Case insensitive removal of a substring if it is at the beginning of a source string,
+ * otherwise returns the source string.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.removeStartIgnoreCase(null, *) = null
+ * StringUtils.removeStartIgnoreCase("", *) = ""
+ * StringUtils.removeStartIgnoreCase(*, null) = *
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStartIgnoreCase("abc", "") = "abc"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeStartIgnoreCase(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (startsWithIgnoreCase(str, remove)) {
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * <p>Removes a substring only if it is at the end of a source string,
+ * otherwise returns the source string.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.endsWith(remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * <p>Case insensitive removal of a substring if it is at the end of a source string,
+ * otherwise returns the source string.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.removeEndIgnoreCase(null, *) = null
+ * StringUtils.removeEndIgnoreCase("", *) = ""
+ * StringUtils.removeEndIgnoreCase(*, null) = *
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("abc", "") = "abc"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
+ * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeEndIgnoreCase(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (endsWithIgnoreCase(str, remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * <p>Removes all occurrences of a substring from within the source string.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove(*, null) = *
+ * StringUtils.remove(*, "") = *
+ * StringUtils.remove("queued", "ue") = "qd"
+ * StringUtils.remove("queued", "zz") = "queued"
+ * </pre>
+ *
+ * @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(String str, String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ return replace(str, remove, EMPTY, -1);
+ }
+
+ /**
+ * <p>Removes all occurrences of a character from within the source string.</p>
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.</p>
+ *
+ * <pre>
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove("queued", 'u') = "qeed"
+ * StringUtils.remove("queued", 'z') = "queued"
+ * </pre>
+ *
+ * @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(String str, char remove) {
+ if (isEmpty(str) || str.indexOf(remove) == INDEX_NOT_FOUND) {
+ return str;
+ }
+ 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);
+ }
+
+ // Replacing
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Replaces a String with another String inside a larger String, once.</p>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String text, String searchString, String replacement) {
+ return replace(text, searchString, replacement, 1);
+ }
+
+ /**
+ * <p>Replaces all occurrences of a String within another String.</p>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String text, String searchString, String replacement) {
+ return replace(text, searchString, replacement, -1);
+ }
+
+ /**
+ * <p>Replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String.</p>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String text, String searchString, 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;
+ }
+ int replLength = searchString.length();
+ int increase = replacement.length() - replLength;
+ increase = (increase < 0 ? 0 : increase);
+ increase *= (max < 0 ? 16 : (max > 64 ? 64 : max));
+ 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();
+ }
+
+ /**
+ * <p>
+ * Replaces all occurrences of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String text, String[] searchList, String[] replacementList) {
+ return replaceEach(text, searchList, replacementList, false, 0);
+ }
+
+ /**
+ * <p>
+ * Replaces all occurrences of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <pre>
+ * 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 repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, true) = IllegalStateException
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, false) = "dcabe"
+ * </pre>
+ *
+ * @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(String text, String[] searchList, String[] replacementList) {
+ // timeToLive should be 0 if not used or nothing to replace, else it's
+ // the length of the replace array
+ int timeToLive = searchList == null ? 0 : searchList.length;
+ return replaceEach(text, searchList, replacementList, true, timeToLive);
+ }
+
+ /**
+ * <p>
+ * Replaces all occurrences of Strings within another String.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <pre>
+ * 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 repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
+ * </pre>
+ *
+ * @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(
+ String text, String[] searchList, String[] replacementList, boolean repeat, 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.length() == 0 || 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");
+ }
+
+ int searchLength = searchList.length;
+ 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
+ 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].length() == 0 || 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;
+ }
+ 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);
+
+ 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].length() == 0 || 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
+
+ }
+ int textLength = text.length();
+ for (int i = start; i < textLength; i++) {
+ buf.append(text.charAt(i));
+ }
+ String result = buf.toString();
+ if (!repeat) {
+ return result;
+ }
+
+ return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
+ }
+
+ // Replace, character based
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Replaces all occurrences of a character in a String with another.
+ * This is a null-safe version of {@link String#replace(char, char)}.</p>
+ *
+ * <p>A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.</p>
+ *
+ * <pre>
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+ * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+ * </pre>
+ *
+ * @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(String str, char searchChar, char replaceChar) {
+ if (str == null) {
+ return null;
+ }
+ return str.replace(searchChar, replaceChar);
+ }
+
+ /**
+ * <p>Replaces multiple characters in a String in one go.
+ * This method can also be used to delete characters.</p>
+ *
+ * <p>For example:<br />
+ * <code>replaceChars(&quot;hello&quot;, &quot;ho&quot;, &quot;jy&quot;) = jelly</code>.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String searchChars, String replaceChars) {
+ if (isEmpty(str) || isEmpty(searchChars)) {
+ return str;
+ }
+ if (replaceChars == null) {
+ replaceChars = EMPTY;
+ }
+ boolean modified = false;
+ int replaceCharsLength = replaceChars.length();
+ int strLength = str.length();
+ StringBuilder buf = new StringBuilder(strLength);
+ for (int i = 0; i < strLength; i++) {
+ char ch = str.charAt(i);
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Overlays part of a String with another String.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String overlay, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+ if (overlay == null) {
+ overlay = EMPTY;
+ }
+ 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) {
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Removes one newline from end of a String if it's there,
+ * otherwise leave it alone. A newline is &quot;{@code \n}&quot;,
+ * &quot;{@code \r}&quot;, or &quot;{@code \r\n}&quot;.</p>
+ *
+ * <p>NOTE: This method changed in 2.0.
+ * It now more closely matches Perl chomp.</p>
+ *
+ * <pre>
+ * StringUtils.chomp(null) = null
+ * StringUtils.chomp("") = ""
+ * StringUtils.chomp("abc \r") = "abc "
+ * StringUtils.chomp("abc\n") = "abc"
+ * StringUtils.chomp("abc\r\n") = "abc"
+ * StringUtils.chomp("abc\r\n\r\n") = "abc\r\n"
+ * StringUtils.chomp("abc\n\r") = "abc\n"
+ * StringUtils.chomp("abc\n\rabc") = "abc\n\rabc"
+ * StringUtils.chomp("\r") = ""
+ * StringUtils.chomp("\n") = ""
+ * StringUtils.chomp("\r\n") = ""
+ * </pre>
+ *
+ * @param str the String to chomp a newline from, may be null
+ * @return String without newline, {@code null} if null String input
+ */
+ public static String chomp(String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+
+ if (str.length() == 1) {
+ char ch = str.charAt(0);
+ if (ch == CharUtils.CR || ch == CharUtils.LF) {
+ return EMPTY;
+ }
+ return str;
+ }
+
+ int lastIdx = str.length() - 1;
+ char last = str.charAt(lastIdx);
+
+ if (last == CharUtils.LF) {
+ if (str.charAt(lastIdx - 1) == CharUtils.CR) {
+ lastIdx--;
+ }
+ } else if (last != CharUtils.CR) {
+ lastIdx++;
+ }
+ return str.substring(0, lastIdx);
+ }
+
+ /**
+ * <p>Removes {@code separator} from the end of
+ * {@code str} if it's there, otherwise leave it alone.</p>
+ *
+ * <p>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)}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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
+ */
+ @Deprecated
+ public static String chomp(String str, String separator) {
+ return removeEnd(str,separator);
+ }
+
+ // Chopping
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Remove the last character from a String.</p>
+ *
+ * <p>If the String ends in {@code \r\n}, then remove both
+ * of them.</p>
+ *
+ * <pre>
+ * StringUtils.chop(null) = null
+ * StringUtils.chop("") = ""
+ * StringUtils.chop("abc \r") = "abc "
+ * StringUtils.chop("abc\n") = "abc"
+ * StringUtils.chop("abc\r\n") = "abc"
+ * StringUtils.chop("abc") = "ab"
+ * StringUtils.chop("abc\nabc") = "abc\nab"
+ * StringUtils.chop("a") = ""
+ * StringUtils.chop("\r") = ""
+ * StringUtils.chop("\n") = ""
+ * StringUtils.chop("\r\n") = ""
+ * </pre>
+ *
+ * @param str the String to chop last character from, may be null
+ * @return String without last character, {@code null} if null String input
+ */
+ public static String chop(String str) {
+ if (str == null) {
+ return null;
+ }
+ int strLen = str.length();
+ if (strLen < 2) {
+ return EMPTY;
+ }
+ int lastIdx = strLen - 1;
+ String ret = str.substring(0, lastIdx);
+ char last = str.charAt(lastIdx);
+ if (last == CharUtils.LF && ret.charAt(lastIdx - 1) == CharUtils.CR) {
+ return ret.substring(0, lastIdx - 1);
+ }
+ return ret;
+ }
+
+ // Conversion
+ //-----------------------------------------------------------------------
+
+ // Padding
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Repeat a String {@code repeat} times to form a
+ * new String.</p>
+ *
+ * <pre>
+ * 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) = ""
+ * </pre>
+ *
+ * @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(String str, int repeat) {
+ // Performance tuned for 2.0 (JDK1.4)
+
+ if (str == null) {
+ return null;
+ }
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ int inputLength = str.length();
+ if (repeat == 1 || inputLength == 0) {
+ return str;
+ }
+ if (inputLength == 1 && repeat <= PAD_LIMIT) {
+ return repeat(str.charAt(0), repeat);
+ }
+
+ int outputLength = inputLength * repeat;
+ switch (inputLength) {
+ case 1 :
+ return repeat(str.charAt(0), repeat);
+ case 2 :
+ char ch0 = str.charAt(0);
+ char ch1 = str.charAt(1);
+ 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 :
+ StringBuilder buf = new StringBuilder(outputLength);
+ for (int i = 0; i < repeat; i++) {
+ buf.append(str);
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * <p>Repeat a String {@code repeat} times to form a
+ * new String, with a String separator injected each time. </p>
+ *
+ * <pre>
+ * 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) = "?, ?, ?"
+ * </pre>
+ *
+ * @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(String str, String separator, int repeat) {
+ if(str == null || separator == null) {
+ return repeat(str, repeat);
+ } else {
+ // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it
+ String result = repeat(str + separator, repeat);
+ return removeEnd(result, separator);
+ }
+ }
+
+ /**
+ * <p>Returns padding using the specified delimiter repeated
+ * to a given length.</p>
+ *
+ * <pre>
+ * StringUtils.repeat(0, 'e') = ""
+ * StringUtils.repeat(3, 'e') = "eee"
+ * StringUtils.repeat(-2, 'e') = ""
+ * </pre>
+ *
+ * <p>Note: this method doesn't not support padding with
+ * <a href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary Characters</a>
+ * 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.
+ * </p>
+ *
+ * @param ch character to repeat
+ * @param repeat number of times to repeat char, negative treated as zero
+ * @return String with repeated character
+ * @see #repeat(String, int)
+ */
+ public static String repeat(char ch, int repeat) {
+ char[] buf = new char[repeat];
+ for (int i = repeat - 1; i >= 0; i--) {
+ buf[i] = ch;
+ }
+ return new String(buf);
+ }
+
+ /**
+ * <p>Right pad a String with spaces (' ').</p>
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int size) {
+ return rightPad(str, size, ' ');
+ }
+
+ /**
+ * <p>Right pad a String with a specified character.</p>
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ }
+ 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));
+ }
+
+ /**
+ * <p>Right pad a String with a specified String.</p>
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * 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 "
+ * </pre>
+ *
+ * @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(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int padLen = padStr.length();
+ int strLen = str.length();
+ 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 {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return str.concat(new String(padding));
+ }
+ }
+
+ /**
+ * <p>Left pad a String with spaces (' ').</p>
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int size) {
+ return leftPad(str, size, ' ');
+ }
+
+ /**
+ * <p>Left pad a String with a specified character.</p>
+ *
+ * <p>Pad to a size of {@code size}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ }
+ 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);
+ }
+
+ /**
+ * <p>Left pad a String with a specified String.</p>
+ *
+ * <p>Pad to a size of {@code size}.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int padLen = padStr.length();
+ int strLen = str.length();
+ 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 {
+ char[] padding = new char[pads];
+ 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(CharSequence cs) {
+ return cs == null ? 0 : cs.length();
+ }
+
+ // Centering
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Centers a String in a larger String of size {@code size}
+ * using the space character (' ').<p>
+ *
+ * <p>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.</p>
+ *
+ * <p>Equivalent to {@code center(str, size, " ")}.</p>
+ *
+ * <pre>
+ * 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 "
+ * </pre>
+ *
+ * @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(String str, int size) {
+ return center(str, size, ' ');
+ }
+
+ /**
+ * <p>Centers a String in a larger String of size {@code size}.
+ * Uses a supplied character as the value to pad the String with.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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, int size, char padChar) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padChar);
+ str = rightPad(str, size, padChar);
+ return str;
+ }
+
+ /**
+ * <p>Centers a String in a larger String of size {@code size}.
+ * Uses a supplied String as the value to pad the String with.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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 "
+ * </pre>
+ *
+ * @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, int size, String padStr) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+ int strLen = str.length();
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts a String to upper case as per {@link String#toUpperCase()}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.upperCase(null) = null
+ * StringUtils.upperCase("") = ""
+ * StringUtils.upperCase("aBc") = "ABC"
+ * </pre>
+ *
+ * <p><strong>Note:</strong> 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}).</p>
+ *
+ * @param str the String to upper case, may be null
+ * @return the upper cased String, {@code null} if null String input
+ */
+ public static String upperCase(String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase();
+ }
+
+ /**
+ * <p>Converts a String to upper case as per {@link String#toUpperCase(Locale)}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.upperCase(null, Locale.ENGLISH) = null
+ * StringUtils.upperCase("", Locale.ENGLISH) = ""
+ * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+ * </pre>
+ *
+ * @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(String str, Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase(locale);
+ }
+
+ /**
+ * <p>Converts a String to lower case as per {@link String#toLowerCase()}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.lowerCase(null) = null
+ * StringUtils.lowerCase("") = ""
+ * StringUtils.lowerCase("aBc") = "abc"
+ * </pre>
+ *
+ * <p><strong>Note:</strong> 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}).</p>
+ *
+ * @param str the String to lower case, may be null
+ * @return the lower cased String, {@code null} if null String input
+ */
+ public static String lowerCase(String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase();
+ }
+
+ /**
+ * <p>Converts a String to lower case as per {@link String#toLowerCase(Locale)}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.lowerCase(null, Locale.ENGLISH) = null
+ * StringUtils.lowerCase("", Locale.ENGLISH) = ""
+ * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+ * </pre>
+ *
+ * @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(String str, Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase(locale);
+ }
+
+ /**
+ * <p>Capitalizes a String changing the first letter to title case as
+ * per {@link Character#toTitleCase(char)}. No other letters are changed.</p>
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#capitalize(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.capitalize(null) = null
+ * StringUtils.capitalize("") = ""
+ * StringUtils.capitalize("cat") = "Cat"
+ * StringUtils.capitalize("cAt") = "CAt"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, {@code null} if null String input
+ * @see org.apache.commons.lang3.text.WordUtils#capitalize(String)
+ * @see #uncapitalize(String)
+ * @since 2.0
+ */
+ public static String capitalize(String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ return new StringBuilder(strLen)
+ .append(Character.toTitleCase(str.charAt(0)))
+ .append(str.substring(1))
+ .toString();
+ }
+
+ /**
+ * <p>Uncapitalizes a String changing the first letter to title case as
+ * per {@link Character#toLowerCase(char)}. No other letters are changed.</p>
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#uncapitalize(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.uncapitalize(null) = null
+ * StringUtils.uncapitalize("") = ""
+ * StringUtils.uncapitalize("Cat") = "cat"
+ * StringUtils.uncapitalize("CAT") = "cAT"
+ * </pre>
+ *
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, {@code null} if null String input
+ * @see org.apache.commons.lang3.text.WordUtils#uncapitalize(String)
+ * @see #capitalize(String)
+ * @since 2.0
+ */
+ public static String uncapitalize(String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ return new StringBuilder(strLen)
+ .append(Character.toLowerCase(str.charAt(0)))
+ .append(str.substring(1))
+ .toString();
+ }
+
+ /**
+ * <p>Swaps the case of a String changing upper and title case to
+ * lower case, and lower case to upper case.</p>
+ *
+ * <ul>
+ * <li>Upper case character converts to Lower case</li>
+ * <li>Title case character converts to Lower case</li>
+ * <li>Lower case character converts to Upper case</li>
+ * </ul>
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#swapCase(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ * </pre>
+ *
+ * <p>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.</p>
+ *
+ * @param str the String to swap case, may be null
+ * @return the changed String, {@code null} if null String input
+ */
+ public static String swapCase(String str) {
+ if (StringUtils.isEmpty(str)) {
+ return str;
+ }
+
+ char[] buffer = str.toCharArray();
+
+ for (int i = 0; i < buffer.length; i++) {
+ char ch = buffer[i];
+ if (Character.isUpperCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ } else if (Character.isTitleCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ } else if (Character.isLowerCase(ch)) {
+ buffer[i] = Character.toUpperCase(ch);
+ }
+ }
+ return new String(buffer);
+ }
+
+ // Count matches
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Counts how many times the substring appears in the larger string.</p>
+ *
+ * <p>A {@code null} or empty ("") String input returns {@code 0}.</p>
+ *
+ * <pre>
+ * StringUtils.countMatches(null, *) = 0
+ * StringUtils.countMatches("", *) = 0
+ * StringUtils.countMatches("abba", null) = 0
+ * StringUtils.countMatches("abba", "") = 0
+ * StringUtils.countMatches("abba", "a") = 2
+ * StringUtils.countMatches("abba", "ab") = 1
+ * StringUtils.countMatches("abba", "xxx") = 0
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param sub the substring to count, may be null
+ * @return the number of occurrences, 0 if either CharSequence is {@code null}
+ * @since 3.0 Changed signature from countMatches(String, String) to countMatches(CharSequence, CharSequence)
+ */
+ public static int countMatches(CharSequence str, CharSequence sub) {
+ if (isEmpty(str) || isEmpty(sub)) {
+ return 0;
+ }
+ int count = 0;
+ int idx = 0;
+ while ((idx = CharSequenceUtils.indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
+ count++;
+ idx += sub.length();
+ }
+ return count;
+ }
+
+ // Character Tests
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if the CharSequence contains only Unicode letters.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAlpha(null) = false
+ * StringUtils.isAlpha("") = false
+ * StringUtils.isAlpha(" ") = false
+ * StringUtils.isAlpha("abc") = true
+ * StringUtils.isAlpha("ab2c") = false
+ * StringUtils.isAlpha("ab-c") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetter(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only Unicode letters and
+ * space (' ').</p>
+ *
+ * <p>{@code null} will return {@code false}
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ 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;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only Unicode letters or digits.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLetterOrDigit(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only Unicode letters, digits
+ * or space ({@code ' '}).</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ 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;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only ASCII printable characters.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isAsciiPrintable(null) = false
+ * StringUtils.isAsciiPrintable("") = true
+ * StringUtils.isAsciiPrintable(" ") = true
+ * StringUtils.isAsciiPrintable("Ceki") = true
+ * StringUtils.isAsciiPrintable("ab2c") = true
+ * StringUtils.isAsciiPrintable("!ab-c~") = true
+ * StringUtils.isAsciiPrintable("\u0020") = true
+ * StringUtils.isAsciiPrintable("\u0021") = true
+ * StringUtils.isAsciiPrintable("\u007e") = true
+ * StringUtils.isAsciiPrintable("\u007f") = false
+ * StringUtils.isAsciiPrintable("Ceki G\u00fclc\u00fc") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if every character is in the range
+ * 32 thru 126
+ * @since 2.1
+ * @since 3.0 Changed signature from isAsciiPrintable(String) to isAsciiPrintable(CharSequence)
+ */
+ public static boolean isAsciiPrintable(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (CharUtils.isAsciiPrintable(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only Unicode digits.
+ * A decimal point is not a Unicode digit and returns false.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isNumeric(null) = false
+ * StringUtils.isNumeric("") = false
+ * StringUtils.isNumeric(" ") = false
+ * StringUtils.isNumeric("123") = true
+ * StringUtils.isNumeric("12 3") = false
+ * StringUtils.isNumeric("ab2c") = false
+ * StringUtils.isNumeric("12-3") = false
+ * StringUtils.isNumeric("12.3") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null || cs.length() == 0) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isDigit(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only Unicode digits or space
+ * ({@code ' '}).
+ * A decimal point is not a Unicode digit and returns false.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isNumericSpace(null) = false
+ * StringUtils.isNumericSpace("") = true
+ * StringUtils.isNumericSpace(" ") = true
+ * StringUtils.isNumericSpace("123") = true
+ * StringUtils.isNumericSpace("12 3") = true
+ * StringUtils.isNumericSpace("ab2c") = false
+ * StringUtils.isNumericSpace("12-3") = false
+ * StringUtils.isNumericSpace("12.3") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ 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;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only whitespace.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isWhitespace(null) = false
+ * StringUtils.isWhitespace("") = true
+ * StringUtils.isWhitespace(" ") = true
+ * StringUtils.isWhitespace("abc") = false
+ * StringUtils.isWhitespace("ab2c") = false
+ * StringUtils.isWhitespace("ab-c") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if ((Character.isWhitespace(cs.charAt(i)) == false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only lowercase characters.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAllLowerCase(null) = false
+ * StringUtils.isAllLowerCase("") = false
+ * StringUtils.isAllLowerCase(" ") = false
+ * StringUtils.isAllLowerCase("abc") = true
+ * StringUtils.isAllLowerCase("abC") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isLowerCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>Checks if the CharSequence contains only uppercase characters.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty String (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAllUpperCase(null) = false
+ * StringUtils.isAllUpperCase("") = false
+ * StringUtils.isAllUpperCase(" ") = false
+ * StringUtils.isAllUpperCase("ABC") = true
+ * StringUtils.isAllUpperCase("aBC") = false
+ * </pre>
+ *
+ * @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(CharSequence cs) {
+ if (cs == null || isEmpty(cs)) {
+ return false;
+ }
+ int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (Character.isUpperCase(cs.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Defaults
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns either the passed in String,
+ * or if the String is {@code null}, an empty String ("").</p>
+ *
+ * <pre>
+ * StringUtils.defaultString(null) = ""
+ * StringUtils.defaultString("") = ""
+ * StringUtils.defaultString("bat") = "bat"
+ * </pre>
+ *
+ * @see ObjectUtils#toString(Object)
+ * @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(String str) {
+ return str == null ? EMPTY : str;
+ }
+
+ /**
+ * <p>Returns either the passed in String, or if the String is
+ * {@code null}, the value of {@code defaultStr}.</p>
+ *
+ * <pre>
+ * StringUtils.defaultString(null, "NULL") = "NULL"
+ * StringUtils.defaultString("", "NULL") = ""
+ * StringUtils.defaultString("bat", "NULL") = "bat"
+ * </pre>
+ *
+ * @see ObjectUtils#toString(Object,String)
+ * @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(String str, String defaultStr) {
+ return str == null ? defaultStr : str;
+ }
+
+ /**
+ * <p>Returns either the passed in CharSequence, or if the CharSequence is
+ * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.</p>
+ *
+ * <pre>
+ * StringUtils.defaultIfBlank(null, "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank(" ", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+ * StringUtils.defaultIfBlank("", null) = null
+ * </pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is whitespace, empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static <T extends CharSequence> T defaultIfBlank(T str, T defaultStr) {
+ return StringUtils.isBlank(str) ? defaultStr : str;
+ }
+
+ /**
+ * <p>Returns either the passed in CharSequence, or if the CharSequence is
+ * empty or {@code null}, the value of {@code defaultStr}.</p>
+ *
+ * <pre>
+ * StringUtils.defaultIfEmpty(null, "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty("", "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+ * StringUtils.defaultIfEmpty("", null) = null
+ * </pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static <T extends CharSequence> T defaultIfEmpty(T str, T defaultStr) {
+ return StringUtils.isEmpty(str) ? defaultStr : str;
+ }
+
+ // Reversing
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Reverses a String as per {@link StringBuilder#reverse()}.</p>
+ *
+ * <p>A {@code null} String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.reverse(null) = null
+ * StringUtils.reverse("") = ""
+ * StringUtils.reverse("bat") = "tab"
+ * </pre>
+ *
+ * @param str the String to reverse, may be null
+ * @return the reversed String, {@code null} if null String input
+ */
+ public static String reverse(String str) {
+ if (str == null) {
+ return null;
+ }
+ return new StringBuilder(str).reverse().toString();
+ }
+
+ /**
+ * <p>Reverses a String that is delimited by a specific character.</p>
+ *
+ * <p>The Strings between the delimiters are not reversed.
+ * Thus java.lang.String becomes String.lang.java (if the delimiter
+ * is {@code '.'}).</p>
+ *
+ * <pre>
+ * StringUtils.reverseDelimited(null, *) = null
+ * StringUtils.reverseDelimited("", *) = ""
+ * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
+ * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
+ * </pre>
+ *
+ * @param str the String to reverse, may be null
+ * @param separatorChar the separator character to use
+ * @return the reversed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String reverseDelimited(String str, char separatorChar) {
+ if (str == null) {
+ return null;
+ }
+ // could implement manually, but simple way is to reuse other,
+ // probably slower, methods.
+ String[] strs = split(str, separatorChar);
+ ArrayUtils.reverse(strs);
+ return join(strs, separatorChar);
+ }
+
+ // Abbreviating
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "Now is the time for..."</p>
+ *
+ * <p>Specifically:
+ * <ul>
+ * <li>If {@code str} is less than {@code maxWidth} characters
+ * long, return it.</li>
+ * <li>Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.</li>
+ * <li>If {@code maxWidth} is less than {@code 4}, throw an
+ * {@code IllegalArgumentException}.</li>
+ * <li>In no case will it return a String of length greater than
+ * {@code maxWidth}.</li>
+ * </ul>
+ * </p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(String str, int maxWidth) {
+ return abbreviate(str, 0, maxWidth);
+ }
+
+ /**
+ * <p>Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "...is the time for..."</p>
+ *
+ * <p>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.
+ *
+ * <p>In no case will it return a String of length greater than
+ * {@code maxWidth}.</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(String str, int offset, 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));
+ }
+
+ /**
+ * <p>Abbreviates a String to the length passed, replacing the middle characters with the supplied
+ * replacement String.</p>
+ *
+ * <p>This abbreviation only occurs if the following criteria is met:
+ * <ul>
+ * <li>Neither the String for abbreviation nor the replacement String are null or empty </li>
+ * <li>The length to truncate to is less than the length of the supplied String</li>
+ * <li>The length to truncate to is greater than 0</li>
+ * <li>The abbreviated String will have enough room for the length supplied replacement String
+ * and the first and last characters of the supplied String for abbreviation</li>
+ * </ul>
+ * Otherwise, the returned String will be the same as the supplied String for abbreviation.
+ * </p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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(String str, String middle, int length) {
+ if (isEmpty(str) || isEmpty(middle)) {
+ return str;
+ }
+
+ if (length >= str.length() || length < (middle.length()+2)) {
+ return str;
+ }
+
+ int targetSting = length-middle.length();
+ int startOffset = targetSting/2+targetSting%2;
+ int endOffset = str.length()-targetSting/2;
+
+ StringBuilder builder = new StringBuilder(length);
+ builder.append(str.substring(0,startOffset));
+ builder.append(middle);
+ builder.append(str.substring(endOffset));
+
+ return builder.toString();
+ }
+
+ // Difference
+ //-----------------------------------------------------------------------
+ /**
+ * <p>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.)</p>
+ *
+ * <p>For example,
+ * {@code difference("i am a machine", "i am a robot") -> "robot"}.</p>
+ *
+ * <pre>
+ * StringUtils.difference(null, null) = null
+ * StringUtils.difference("", "") = ""
+ * StringUtils.difference("", "abc") = "abc"
+ * StringUtils.difference("abc", "") = ""
+ * StringUtils.difference("abc", "abc") = ""
+ * StringUtils.difference("ab", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "xyz") = "xyz"
+ * </pre>
+ *
+ * @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
+ * @since 2.0
+ */
+ public static String difference(String str1, String str2) {
+ if (str1 == null) {
+ return str2;
+ }
+ if (str2 == null) {
+ return str1;
+ }
+ int at = indexOfDifference(str1, str2);
+ if (at == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str2.substring(at);
+ }
+
+ /**
+ * <p>Compares two CharSequences, and returns the index at which the
+ * CharSequences begin to differ.</p>
+ *
+ * <p>For example,
+ * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}</p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence cs1, 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;
+ }
+
+ /**
+ * <p>Compares all CharSequences in an array and returns the index at which the
+ * CharSequences begin to differ.</p>
+ *
+ * <p>For example,
+ * <code>indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7</code></p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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(CharSequence... css) {
+ if (css == null || css.length <= 1) {
+ return INDEX_NOT_FOUND;
+ }
+ boolean anyStringNull = false;
+ boolean allStringsNull = true;
+ 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++) {
+ 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;
+ }
+
+ /**
+ * <p>Compares all Strings in an array and returns the initial sequence of
+ * characters that is common to all of them.</p>
+ *
+ * <p>For example,
+ * <code>getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "</code></p>
+ *
+ * <pre>
+ * 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 "
+ * </pre>
+ *
+ * @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(String... strs) {
+ if (strs == null || strs.length == 0) {
+ return EMPTY;
+ }
+ 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
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Find the Levenshtein distance between two Strings.</p>
+ *
+ * <p>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).</p>
+ *
+ * <p>The previous implementation of the Levenshtein distance algorithm
+ * was from <a href="http://www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</a></p>
+ *
+ * <p>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.<br>
+ * This implementation of the Levenshtein distance algorithm
+ * is from <a href="http://www.merriampark.com/ldjava.htm">http://www.merriampark.com/ldjava.htm</a></p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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
+ 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];
+ }
+
+ /**
+ * <p>Find the Levenshtein distance between two Strings if it's less than or equal to a given
+ * threshold.</p>
+ *
+ * <p>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).</p>
+ *
+ * <p>This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield
+ * and Chas Emerick's implementation of the Levenshtein distance algorithm from
+ * <a href="http://www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</a></p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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, 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
+ 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
+ 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++) {
+ char t_j = t.charAt(j - 1); // jth character of t
+ d[0] = j;
+
+ // compute stripe indices, constrain to array size
+ int min = Math.max(1, j - threshold);
+ int max = 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];
+ } else {
+ return -1;
+ }
+ }
+
+ // startsWith
+ //-----------------------------------------------------------------------
+
+ /**
+ * <p>Check if a CharSequence starts with a specified prefix.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive.</p>
+ *
+ * <pre>
+ * StringUtils.startsWith(null, null) = true
+ * StringUtils.startsWith(null, "abc") = false
+ * StringUtils.startsWith("abcdef", null) = false
+ * StringUtils.startsWith("abcdef", "abc") = true
+ * StringUtils.startsWith("ABCDEF", "abc") = false
+ * </pre>
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence)
+ */
+ public static boolean startsWith(CharSequence str, CharSequence prefix) {
+ return startsWith(str, prefix, false);
+ }
+
+ /**
+ * <p>Case insensitive check if a CharSequence starts with a specified prefix.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.startsWithIgnoreCase(null, null) = true
+ * StringUtils.startsWithIgnoreCase(null, "abc") = false
+ * StringUtils.startsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+ * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+ * </pre>
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean startsWithIgnoreCase(CharSequence str, CharSequence prefix) {
+ return startsWith(str, prefix, true);
+ }
+
+ /**
+ * <p>Check if a CharSequence starts with a specified prefix (optionally case insensitive).</p>
+ *
+ * @see java.lang.String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean startsWith(CharSequence str, CharSequence prefix, boolean ignoreCase) {
+ if (str == null || prefix == null) {
+ return (str == null && prefix == null);
+ }
+ if (prefix.length() > str.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * <p>Check if a CharSequence starts with any of an array of specified strings.</p>
+ *
+ * <pre>
+ * StringUtils.startsWithAny(null, null) = false
+ * StringUtils.startsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.startsWithAny("abcxyz", null) = false
+ * StringUtils.startsWithAny("abcxyz", new String[] {""}) = false
+ * StringUtils.startsWithAny("abcxyz", new String[] {"abc"}) = true
+ * StringUtils.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ * </pre>
+ *
+ * @param string the CharSequence to check, may be null
+ * @param searchStrings the CharSequences to find, may be null or empty
+ * @return {@code true} if the CharSequence starts with any of the the prefixes, case insensitive, or
+ * both {@code null}
+ * @since 2.5
+ * @since 3.0 Changed signature from startsWithAny(String, String[]) to startsWithAny(CharSequence, CharSequence...)
+ */
+ public static boolean startsWithAny(CharSequence string, CharSequence... searchStrings) {
+ if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (CharSequence searchString : searchStrings) {
+ if (StringUtils.startsWith(string, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // endsWith
+ //-----------------------------------------------------------------------
+
+ /**
+ * <p>Check if a CharSequence ends with a specified suffix.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case sensitive.</p>
+ *
+ * <pre>
+ * StringUtils.endsWith(null, null) = true
+ * StringUtils.endsWith(null, "def") = false
+ * StringUtils.endsWith("abcdef", null) = false
+ * StringUtils.endsWith("abcdef", "def") = true
+ * StringUtils.endsWith("ABCDEF", "def") = false
+ * StringUtils.endsWith("ABCDEF", "cde") = false
+ * </pre>
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence)
+ */
+ public static boolean endsWith(CharSequence str, CharSequence suffix) {
+ return endsWith(str, suffix, false);
+ }
+
+ /**
+ * <p>Case insensitive check if a CharSequence ends with a specified suffix.</p>
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.endsWithIgnoreCase(null, null) = true
+ * StringUtils.endsWithIgnoreCase(null, "def") = false
+ * StringUtils.endsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
+ * </pre>
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean endsWithIgnoreCase(CharSequence str, CharSequence suffix) {
+ return endsWith(str, suffix, true);
+ }
+
+ /**
+ * <p>Check if a CharSequence ends with a specified suffix (optionally case insensitive).</p>
+ *
+ * @see java.lang.String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean endsWith(CharSequence str, CharSequence suffix, boolean ignoreCase) {
+ if (str == null || suffix == null) {
+ return str == null && suffix == null;
+ }
+ if (suffix.length() > str.length()) {
+ return false;
+ }
+ int strOffset = str.length() - suffix.length();
+ return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length());
+ }
+
+ /**
+ * <p>
+ * Similar to <a
+ * href="http://www.w3.org/TR/xpath/#function-normalize-space">http://www.w3.org/TR/xpath/#function-normalize
+ * -space</a>
+ * </p>
+ * <p>
+ * The function returns the argument string with whitespace normalized by using
+ * <code>{@link #trim(String)}</code> to remove leading and trailing whitespace
+ * and then replacing sequences of whitespace characters by a single space.
+ * </p>
+ * In XML Whitespace characters are the same as those allowed by the <a
+ * href="http://www.w3.org/TR/REC-xml/#NT-S">S</a> production, which is S ::= (#x20 | #x9 | #xD | #xA)+
+ * <p>
+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r]
+ * <p>
+ * For reference:
+ * <ul>
+ * <li>\x0B = vertical tab</li>
+ * <li>\f = #xC = form feed</li>
+ * <li>#x20 = space</li>
+ * <li>#x9 = \t</li>
+ * <li>#xA = \n</li>
+ * <li>#xD = \r</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also
+ * normalize. Additionally <code>{@link #trim(String)}</code> removes control characters (char &lt;= 32) from both
+ * ends of this String.
+ * </p>
+ *
+ * @see Pattern
+ * @see #trim(String)
+ * @see <a
+ * href="http://www.w3.org/TR/xpath/#function-normalize-space">http://www.w3.org/TR/xpath/#function-normalize-space</a>
+ * @param str the source String to normalize whitespaces from, may be null
+ * @return the modified string with whitespace normalized, {@code null} if null String input
+ *
+ * @since 3.0
+ */
+ public static String normalizeSpace(String str) {
+ if (str == null) {
+ return null;
+ }
+ return WHITESPACE_BLOCK.matcher(trim(str)).replaceAll(" ");
+ }
+
+ /**
+ * <p>Check if a CharSequence ends with any of an array of specified strings.</p>
+ *
+ * <pre>
+ * StringUtils.endsWithAny(null, null) = false
+ * StringUtils.endsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.endsWithAny("abcxyz", null) = false
+ * StringUtils.endsWithAny("abcxyz", new String[] {""}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {"xyz"}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ * </pre>
+ *
+ * @param string the CharSequence to check, may be null
+ * @param searchStrings the CharSequences to find, may be null or empty
+ * @return {@code true} if the CharSequence ends with any of the the prefixes, case insensitive, or
+ * both {@code null}
+ * @since 3.0
+ */
+ public static boolean endsWithAny(CharSequence string, CharSequence... searchStrings) {
+ if (isEmpty(string) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (CharSequence searchString : searchStrings) {
+ if (StringUtils.endsWith(string, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/src/org/apache/commons/lang3/SystemUtils.java b/src/org/apache/commons/lang3/SystemUtils.java
new file mode 100644
index 0000000..c6fb73f
--- /dev/null
+++ b/src/org/apache/commons/lang3/SystemUtils.java
@@ -0,0 +1,1419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.io.File;
+
+/**
+ * <p>
+ * Helpers for {@code java.lang.System}.
+ * </p>
+ * <p>
+ * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set
+ * to {@code null} and a message will be written to {@code System.err}.
+ * </p>
+ * <p>
+ * #ThreadSafe#
+ * </p>
+ *
+ * @since 1.0
+ * @version $Id: SystemUtils.java 1160564 2011-08-23 06:56:42Z bayard $
+ */
+public class SystemUtils {
+
+ /**
+ * The prefix String for all Windows OS.
+ */
+ private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
+
+ // System property constants
+ // -----------------------------------------------------------------------
+ // These MUST be declared first. Other constants depend on this.
+
+ /**
+ * The System property key for the user home directory.
+ */
+ private static final String USER_HOME_KEY = "user.home";
+
+ /**
+ * The System property key for the user directory.
+ */
+ private static final String USER_DIR_KEY = "user.dir";
+
+ /**
+ * The System property key for the Java IO temporary directory.
+ */
+ private static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
+
+ /**
+ * The System property key for the Java home directory.
+ */
+ private static final String JAVA_HOME_KEY = "java.home";
+
+ /**
+ * <p>
+ * The {@code awt.toolkit} System Property.
+ * </p>
+ * <p>
+ * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}.
+ * </p>
+ * <p>
+ * <b>On platforms without a GUI, this value is {@code null}.</b>
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final String AWT_TOOLKIT = getSystemProperty("awt.toolkit");
+
+ /**
+ * <p>
+ * The {@code file.encoding} System Property.
+ * </p>
+ * <p>
+ * File encoding, such as {@code Cp1252}.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String FILE_ENCODING = getSystemProperty("file.encoding");
+
+ /**
+ * <p>
+ * The {@code file.separator} System Property. File separator (<code>&quot;/&quot;</code> on UNIX).
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String FILE_SEPARATOR = getSystemProperty("file.separator");
+
+ /**
+ * <p>
+ * The {@code java.awt.fonts} System Property.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_FONTS = getSystemProperty("java.awt.fonts");
+
+ /**
+ * <p>
+ * The {@code java.awt.graphicsenv} System Property.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_GRAPHICSENV = getSystemProperty("java.awt.graphicsenv");
+
+ /**
+ * <p>
+ * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or
+ * {@code "false"}.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see #isJavaAwtHeadless()
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_AWT_HEADLESS = getSystemProperty("java.awt.headless");
+
+ /**
+ * <p>
+ * The {@code java.awt.printerjob} System Property.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_PRINTERJOB = getSystemProperty("java.awt.printerjob");
+
+ /**
+ * <p>
+ * The {@code java.class.path} System Property. Java class path.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_PATH = getSystemProperty("java.class.path");
+
+ /**
+ * <p>
+ * The {@code java.class.version} System Property. Java class format version number.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_VERSION = getSystemProperty("java.class.version");
+
+ /**
+ * <p>
+ * The {@code java.compiler} System Property. Name of JIT compiler to use. First in JDK version 1.2. Not used in Sun
+ * JDKs after 1.2.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2. Not used in Sun versions after 1.2.
+ */
+ public static final String JAVA_COMPILER = getSystemProperty("java.compiler");
+
+ /**
+ * <p>
+ * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.4
+ */
+ public static final String JAVA_ENDORSED_DIRS = getSystemProperty("java.endorsed.dirs");
+
+ /**
+ * <p>
+ * The {@code java.ext.dirs} System Property. Path of extension directory or directories.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.3
+ */
+ public static final String JAVA_EXT_DIRS = getSystemProperty("java.ext.dirs");
+
+ /**
+ * <p>
+ * The {@code java.home} System Property. Java installation directory.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_HOME = getSystemProperty(JAVA_HOME_KEY);
+
+ /**
+ * <p>
+ * The {@code java.io.tmpdir} System Property. Default temp file path.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_IO_TMPDIR = getSystemProperty(JAVA_IO_TMPDIR_KEY);
+
+ /**
+ * <p>
+ * The {@code java.library.path} System Property. List of paths to search when loading libraries.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_LIBRARY_PATH = getSystemProperty("java.library.path");
+
+ /**
+ * <p>
+ * The {@code java.runtime.name} System Property. Java Runtime Environment name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_NAME = getSystemProperty("java.runtime.name");
+
+ /**
+ * <p>
+ * The {@code java.runtime.version} System Property. Java Runtime Environment version.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_VERSION = getSystemProperty("java.runtime.version");
+
+ /**
+ * <p>
+ * The {@code java.specification.name} System Property. Java Runtime Environment specification name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_NAME = getSystemProperty("java.specification.name");
+
+ /**
+ * <p>
+ * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_VENDOR = getSystemProperty("java.specification.vendor");
+
+ /**
+ * <p>
+ * The {@code java.specification.version} System Property. Java Runtime Environment specification version.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.3
+ */
+ public static final String JAVA_SPECIFICATION_VERSION = getSystemProperty("java.specification.version");
+ private static final JavaVersion JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION);
+
+ /**
+ * <p>
+ * The {@code java.util.prefs.PreferencesFactory} System Property. A class name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY =
+ getSystemProperty("java.util.prefs.PreferencesFactory");
+
+ /**
+ * <p>
+ * The {@code java.vendor} System Property. Java vendor-specific string.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR = getSystemProperty("java.vendor");
+
+ /**
+ * <p>
+ * The {@code java.vendor.url} System Property. Java vendor URL.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR_URL = getSystemProperty("java.vendor.url");
+
+ /**
+ * <p>
+ * The {@code java.version} System Property. Java version number.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String JAVA_VERSION = getSystemProperty("java.version");
+
+ /**
+ * <p>
+ * The {@code java.vm.info} System Property. Java Virtual Machine implementation info.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_INFO = getSystemProperty("java.vm.info");
+
+ /**
+ * <p>
+ * The {@code java.vm.name} System Property. Java Virtual Machine implementation name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_NAME = getSystemProperty("java.vm.name");
+
+ /**
+ * <p>
+ * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_NAME = getSystemProperty("java.vm.specification.name");
+
+ /**
+ * <p>
+ * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VENDOR = getSystemProperty("java.vm.specification.vendor");
+
+ /**
+ * <p>
+ * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VERSION = getSystemProperty("java.vm.specification.version");
+
+ /**
+ * <p>
+ * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VENDOR = getSystemProperty("java.vm.vendor");
+
+ /**
+ * <p>
+ * The {@code java.vm.version} System Property. Java Virtual Machine implementation version.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VERSION = getSystemProperty("java.vm.version");
+
+ /**
+ * <p>
+ * The {@code line.separator} System Property. Line separator (<code>&quot;\n&quot;</code> on UNIX).
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String LINE_SEPARATOR = getSystemProperty("line.separator");
+
+ /**
+ * <p>
+ * The {@code os.arch} System Property. Operating system architecture.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_ARCH = getSystemProperty("os.arch");
+
+ /**
+ * <p>
+ * The {@code os.name} System Property. Operating system name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_NAME = getSystemProperty("os.name");
+
+ /**
+ * <p>
+ * The {@code os.version} System Property. Operating system version.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String OS_VERSION = getSystemProperty("os.version");
+
+ /**
+ * <p>
+ * The {@code path.separator} System Property. Path separator (<code>&quot;:&quot;</code> on UNIX).
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String PATH_SEPARATOR = getSystemProperty("path.separator");
+
+ /**
+ * <p>
+ * The {@code user.country} or {@code user.region} System Property. User's country code, such as {@code GB}. First
+ * in Java version 1.2 as {@code user.region}. Renamed to {@code user.country} in 1.4
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_COUNTRY = getSystemProperty("user.country") == null ?
+ getSystemProperty("user.region") : getSystemProperty("user.country");
+
+ /**
+ * <p>
+ * The {@code user.dir} System Property. User's current working directory.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_DIR = getSystemProperty(USER_DIR_KEY);
+
+ /**
+ * <p>
+ * The {@code user.home} System Property. User's home directory.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_HOME = getSystemProperty(USER_HOME_KEY);
+
+ /**
+ * <p>
+ * The {@code user.language} System Property. User's language code, such as {@code "en"}.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_LANGUAGE = getSystemProperty("user.language");
+
+ /**
+ * <p>
+ * The {@code user.name} System Property. User's account name.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since Java 1.1
+ */
+ public static final String USER_NAME = getSystemProperty("user.name");
+
+ /**
+ * <p>
+ * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final String USER_TIMEZONE = getSystemProperty("user.timezone");
+
+ // Java version checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.1 (also 1.1.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.2 (also 1.2.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.3 (also 1.3.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.4 (also 1.4.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.5 (also 1.5.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.6 (also 1.6.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Java version 1.7 (also 1.7.x versions).
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_JAVA_1_7 = getJavaVersionMatches("1.7");
+
+ // Operating system checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+ // OS names from http://www.vamphq.com/os.html
+ // Selected ones included - please advise dev@commons.apache.org
+ // if you want another added or a mistake corrected
+
+ /**
+ * <p>
+ * Is {@code true} if this is AIX.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_AIX = getOSMatchesName("AIX");
+
+ /**
+ * <p>
+ * Is {@code true} if this is HP-UX.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_HP_UX = getOSMatchesName("HP-UX");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Irix.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_IRIX = getOSMatchesName("Irix");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Linux.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_LINUX = getOSMatchesName("Linux") || getOSMatchesName("LINUX");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Mac.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC = getOSMatchesName("Mac");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Mac.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC_OSX = getOSMatchesName("Mac OS X");
+
+ /**
+ * <p>
+ * Is {@code true} if this is FreeBSD.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 3.0.2
+ */
+ public static final boolean IS_OS_FREE_BSD = getOSMatchesName("FreeBSD");
+
+ /**
+ * <p>
+ * Is {@code true} if this is OpenBSD.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 3.0.2
+ */
+ public static final boolean IS_OS_OPEN_BSD = getOSMatchesName("OpenBSD");
+
+ /**
+ * <p>
+ * Is {@code true} if this is NetBSD.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 3.0.2
+ */
+ public static final boolean IS_OS_NET_BSD = getOSMatchesName("NetBSD");
+
+ /**
+ * <p>
+ * Is {@code true} if this is OS/2.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_OS2 = getOSMatchesName("OS/2");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Solaris.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SOLARIS = getOSMatchesName("Solaris");
+
+ /**
+ * <p>
+ * Is {@code true} if this is SunOS.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SUN_OS = getOSMatchesName("SunOS");
+
+ /**
+ * <p>
+ * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final boolean IS_OS_UNIX = IS_OS_AIX || IS_OS_HP_UX || IS_OS_IRIX || IS_OS_LINUX || IS_OS_MAC_OSX
+ || IS_OS_SOLARIS || IS_OS_SUN_OS || IS_OS_FREE_BSD || IS_OS_OPEN_BSD || IS_OS_NET_BSD;
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS = getOSMatchesName(OS_NAME_WINDOWS_PREFIX);
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows 2000.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_2000 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.0");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows 95.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_95 = getOSMatches(OS_NAME_WINDOWS_PREFIX + " 9", "4.0");
+ // Java 1.2 running on Windows98 returns 'Windows 95', hence the above
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows 98.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_98 = getOSMatches(OS_NAME_WINDOWS_PREFIX + " 9", "4.1");
+ // Java 1.2 running on Windows98 returns 'Windows 95', hence the above
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows ME.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_ME = getOSMatches(OS_NAME_WINDOWS_PREFIX, "4.9");
+ // Java 1.2 running on WindowsME may return 'Windows 95', hence the above
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows NT.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_NT = getOSMatchesName(OS_NAME_WINDOWS_PREFIX + " NT");
+ // Windows 2000 returns 'Windows 2000' but may suffer from same Java1.2 problem
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows XP.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_XP = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.1");
+
+ // -----------------------------------------------------------------------
+ /**
+ * <p>
+ * Is {@code true} if this is Windows Vista.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 2.4
+ */
+ public static final boolean IS_OS_WINDOWS_VISTA = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.0");
+
+ /**
+ * <p>
+ * Is {@code true} if this is Windows 7.
+ * </p>
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_OS_WINDOWS_7 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.1");
+
+ /**
+ * <p>
+ * Gets the Java home directory as a {@code File}.
+ * </p>
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getJavaHome() {
+ return new File(System.getProperty(JAVA_HOME_KEY));
+ }
+
+ /**
+ * <p>
+ * Gets the Java IO temporary directory as a {@code File}.
+ * </p>
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getJavaIoTmpDir() {
+ return new File(System.getProperty(JAVA_IO_TMPDIR_KEY));
+ }
+
+ /**
+ * <p>
+ * Decides if the Java version matches.
+ * </p>
+ *
+ * @param versionPrefix the prefix for the java version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getJavaVersionMatches(String versionPrefix) {
+ return isJavaVersionMatch(JAVA_SPECIFICATION_VERSION, versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the os name
+ * @param osVersionPrefix the prefix for the version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOSMatches(String osNamePrefix, String osVersionPrefix) {
+ return isOSMatch(OS_NAME, OS_VERSION, osNamePrefix, osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the os name
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOSMatchesName(String osNamePrefix) {
+ return isOSNameMatch(OS_NAME, osNamePrefix);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * <p>
+ * Gets a System property, defaulting to {@code null} if the property cannot be read.
+ * </p>
+ * <p>
+ * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to
+ * {@code System.err}.
+ * </p>
+ *
+ * @param property the system property name
+ * @return the system property value or {@code null} if a security problem occurs
+ */
+ private static String getSystemProperty(String property) {
+ try {
+ return System.getProperty(property);
+ } catch (SecurityException ex) {
+ // we are not allowed to look at this property
+ System.err.println("Caught a SecurityException reading the system property '" + property
+ + "'; the SystemUtils property value will default to null.");
+ return null;
+ }
+ }
+
+ /**
+ * <p>
+ * Gets the user directory as a {@code File}.
+ * </p>
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getUserDir() {
+ return new File(System.getProperty(USER_DIR_KEY));
+ }
+
+ /**
+ * <p>
+ * Gets the user home directory as a {@code File}.
+ * </p>
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see System#getProperty(String)
+ * @since 2.1
+ */
+ public static File getUserHome() {
+ return new File(System.getProperty(USER_HOME_KEY));
+ }
+
+ /**
+ * Returns whether the {@link #JAVA_AWT_HEADLESS} value is {@code true}.
+ *
+ * @return {@code true} if {@code JAVA_AWT_HEADLESS} is {@code "true"}, {@code false} otherwise.
+ * @see #JAVA_AWT_HEADLESS
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static boolean isJavaAwtHeadless() {
+ return JAVA_AWT_HEADLESS != null ? JAVA_AWT_HEADLESS.equals(Boolean.TRUE.toString()) : false;
+ }
+
+ /**
+ * <p>
+ * Is the Java version at least the requested version.
+ * </p>
+ * <p>
+ * Example input:
+ * </p>
+ * <ul>
+ * <li>{@code 1.2f} to test for Java 1.2</li>
+ * <li>{@code 1.31f} to test for Java 1.3.1</li>
+ * </ul>
+ *
+ * @param requiredVersion the required version, for example 1.31f
+ * @return {@code true} if the actual version is equal or greater than the required version
+ */
+ public static boolean isJavaVersionAtLeast(JavaVersion requiredVersion) {
+ return JAVA_SPECIFICATION_VERSION_AS_ENUM.atLeast(requiredVersion);
+ }
+
+ /**
+ * <p>
+ * Decides if the Java version matches.
+ * </p>
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param version the actual Java version
+ * @param versionPrefix the prefix for the expected Java version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isJavaVersionMatch(String version, String versionPrefix) {
+ if (version == null) {
+ return false;
+ }
+ return version.startsWith(versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param osName the actual OS name
+ * @param osVersion the actual OS version
+ * @param osNamePrefix the prefix for the expected OS name
+ * @param osVersionPrefix the prefix for the expected OS version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSMatch(String osName, String osVersion, String osNamePrefix, String osVersionPrefix) {
+ if (osName == null || osVersion == null) {
+ return false;
+ }
+ return osName.startsWith(osNamePrefix) && osVersion.startsWith(osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param osName the actual OS name
+ * @param osNamePrefix the prefix for the expected OS name
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSNameMatch(String osName, String osNamePrefix) {
+ if (osName == null) {
+ return false;
+ }
+ return osName.startsWith(osNamePrefix);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * <p>
+ * SystemUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code SystemUtils.FILE_SEPARATOR}.
+ * </p>
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public SystemUtils() {
+ super();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/Validate.java b/src/org/apache/commons/lang3/Validate.java
new file mode 100644
index 0000000..36716d6
--- /dev/null
+++ b/src/org/apache/commons/lang3/Validate.java
@@ -0,0 +1,1071 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * <p>This class assists in validating arguments. The validation methods are
+ * based along the following principles:
+ * <ul>
+ * <li>An invalid {@code null} argument causes a {@link NullPointerException}.</li>
+ * <li>A non-{@code null} argument causes an {@link IllegalArgumentException}.</li>
+ * <li>An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.</li>
+ * </ul>
+ *
+ * <p>All exceptions messages are
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/Formatter.html#syntax">format strings</a>
+ * as defined by the Java platform. For example:</p>
+ *
+ * <pre>
+ * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
+ * Validate.notNull(surname, "The surname must not be %s", null);
+ * </pre>
+ *
+ * <p>#ThreadSafe#</p>
+ * @version $Id: Validate.java 1153490 2011-08-03 13:53:35Z ggregory $
+ * @see java.lang.String#format(String, Object...)
+ * @since 2.0
+ */
+public class Validate {
+
+ private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified exclusive range of %s to %s";
+ private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified inclusive range of %s to %s";
+ private static final String DEFAULT_MATCHES_PATTERN_EX = "The string %s does not match the pattern %s";
+ private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null";
+ private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE =
+ "The validated array contains null element at index: %d";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE =
+ "The validated collection contains null element at index: %d";
+ private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank";
+ private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty";
+ private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence is empty";
+ private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty";
+ private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty";
+ private static final String DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE = "The validated array index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE =
+ "The validated collection index is invalid: %d";
+ private static final String DEFAULT_VALID_STATE_EX_MESSAGE = "The validated state is false";
+ private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE =
+ "The validated class can not be converted to the %s class";
+ private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "The validated object is not an instance of %s";
+
+ /**
+ * Constructor. This class should not normally be instantiated.
+ */
+ public Validate() {
+ super();
+ }
+
+ // isTrue
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);</pre>
+ *
+ * <p>For performance reasons, the long value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.</p>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression, String message, long value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, Long.valueOf(value)));
+ }
+ }
+
+ /**
+ * <p>Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);</pre>
+ *
+ * <p>For performance reasons, the double value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.</p>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression, String message, double value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, Double.valueOf(value)));
+ }
+ }
+
+ /**
+ * <p>Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>
+ * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
+ * Validate.isTrue(myObject.isOk(), "The object is not okay");</pre>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ */
+ public static void isTrue(boolean expression, String message, Object... values) {
+ if (expression == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ /**
+ * <p>Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception. This method is useful when validating according
+ * to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>
+ * Validate.isTrue(i > 0);
+ * Validate.isTrue(myObject.isOk());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated expression is
+ * false&quot;.</p>
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(boolean expression) {
+ if (expression == false) {
+ throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE);
+ }
+ }
+
+ // notNull
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument is not {@code null};
+ * otherwise throwing an exception.
+ *
+ * <pre>Validate.notNull(myObject, "The object must not be null");</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object is
+ * null&quot;.</p>
+ *
+ * @param <T> the object type
+ * @param object the object to check
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see #notNull(Object, String, Object...)
+ */
+ public static <T> T notNull(T object) {
+ return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE);
+ }
+
+ /**
+ * <p>Validate that the specified argument is not {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.notNull(myObject, "The object must not be null");</pre>
+ *
+ * @param <T> the object type
+ * @param object the object to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see #notNull(Object)
+ */
+ public static <T> T notNull(T object, String message, Object... values) {
+ if (object == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ return object;
+ }
+
+ // notEmpty array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ * <pre>Validate.notEmpty(myArray, "The array must not be empty");</pre>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T> T[] notEmpty(T[] array, String message, Object... values) {
+ if (array == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (array.length == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return array;
+ }
+
+ /**
+ * <p>Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myArray);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated array is
+ * empty&quot;.
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[], String, Object...)
+ */
+ public static <T> T[] notEmpty(T[] array) {
+ return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE);
+ }
+
+ // notEmpty collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ * <pre>Validate.notEmpty(myCollection, "The collection must not be empty");</pre>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T extends Collection<?>> T notEmpty(T collection, String message, Object... values) {
+ if (collection == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (collection.size() == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return collection;
+ }
+
+ /**
+ * <p>Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myCollection);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated collection is
+ * empty&quot;.</p>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Collection, String, Object...)
+ */
+ public static <T extends Collection<?>> T notEmpty(T collection) {
+ return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE);
+ }
+
+ // notEmpty map
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument map is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ * <pre>Validate.notEmpty(myMap, "The map must not be empty");</pre>
+ *
+ * @param <T> the map type
+ * @param map the map to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T extends Map<?, ?>> T notEmpty(T map, String message, Object... values) {
+ if (map == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (map.size() == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return map;
+ }
+
+ /**
+ * <p>Validate that the specified argument map is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myMap);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated map is
+ * empty&quot;.</p>
+ *
+ * @param <T> the map type
+ * @param map the map to check, validated not null by this method
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Map, String, Object...)
+ */
+ public static <T extends Map<?, ?>> T notEmpty(T map) {
+ return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE);
+ }
+
+ // notEmpty string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.notEmpty(myString, "The string must not be empty");</pre>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence)
+ */
+ public static <T extends CharSequence> T notEmpty(T chars, String message, Object... values) {
+ if (chars == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (chars.length() == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.notEmpty(myString);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated
+ * character sequence is empty&quot;.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence, String, Object...)
+ */
+ public static <T extends CharSequence> T notEmpty(T chars) {
+ return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE);
+ }
+
+ // notBlank string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null}, a length of zero (no characters), empty
+ * nor whitespace; otherwise throwing an exception with the specified
+ * message.
+ *
+ * <pre>Validate.notBlank(myString, "The string must not be blank");</pre>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence)
+ *
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T notBlank(T chars, String message, Object... values) {
+ if (chars == null) {
+ throw new NullPointerException(String.format(message, values));
+ }
+ if (StringUtils.isBlank(chars)) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null}, a length of zero (no characters), empty
+ * nor whitespace; otherwise throwing an exception.
+ *
+ * <pre>Validate.notBlank(myString);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated character
+ * sequence is blank&quot;.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T notBlank(T chars) {
+ return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE);
+ }
+
+ // noNullElements array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument array is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.noNullElements(myArray, "The array contain null at position %d");</pre>
+ *
+ * <p>If the array is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the array has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[])
+ */
+ public static <T> T[] noNullElements(T[] array, String message, Object... values) {
+ Validate.notNull(array);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ Object[] values2 = ArrayUtils.add(values, Integer.valueOf(i));
+ throw new IllegalArgumentException(String.format(message, values2));
+ }
+ }
+ return array;
+ }
+
+ /**
+ * <p>Validate that the specified argument array is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception.
+ *
+ * <pre>Validate.noNullElements(myArray);</pre>
+ *
+ * <p>If the array is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the array has a {@code null} element, then the message in the
+ * exception is &quot;The validated array contains null element at index:
+ * &quot followed by the index.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[], String, Object...)
+ */
+ public static <T> T[] noNullElements(T[] array) {
+ return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE);
+ }
+
+ // noNullElements iterable
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument iterable is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.noNullElements(myCollection, "The collection contains null at position %d");</pre>
+ *
+ * <p>If the iterable is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the iterable has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.</p>
+ *
+ * @param <T> the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable)
+ */
+ public static <T extends Iterable<?>> T noNullElements(T iterable, String message, Object... values) {
+ Validate.notNull(iterable);
+ int i = 0;
+ for (Iterator<?> it = iterable.iterator(); it.hasNext(); i++) {
+ if (it.next() == null) {
+ Object[] values2 = ArrayUtils.addAll(values, Integer.valueOf(i));
+ throw new IllegalArgumentException(String.format(message, values2));
+ }
+ }
+ return iterable;
+ }
+
+ /**
+ * <p>Validate that the specified argument iterable is neither
+ * {@code null} nor contains any elements that are {@code null};
+ * otherwise throwing an exception.
+ *
+ * <pre>Validate.noNullElements(myCollection);</pre>
+ *
+ * <p>If the iterable is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the array has a {@code null} element, then the message in the
+ * exception is &quot;The validated iterable contains null element at index:
+ * &quot followed by the index.</p>
+ *
+ * @param <T> the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable, String, Object...)
+ */
+ public static <T extends Iterable<?>> T noNullElements(T iterable) {
+ return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE);
+ }
+
+ // validIndex array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * array; otherwise throwing an exception with the specified message.</p>
+ *
+ * <pre>Validate.validIndex(myArray, 2, "The array index is invalid: ");</pre>
+ *
+ * <p>If the array is {@code null}, then the message of the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int)
+ *
+ * @since 3.0
+ */
+ public static <T> T[] validIndex(T[] array, int index, String message, Object... values) {
+ Validate.notNull(array);
+ if (index < 0 || index >= array.length) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return array;
+ }
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * array; otherwise throwing an exception.</p>
+ *
+ * <pre>Validate.validIndex(myArray, 2);</pre>
+ *
+ * <p>If the array is {@code null}, then the message of the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the index is invalid, then the message of the exception is
+ * &quot;The validated array index is invalid: &quot; followed by the
+ * index.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T> T[] validIndex(T[] array, int index) {
+ return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validIndex collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * collection; otherwise throwing an exception with the specified message.</p>
+ *
+ * <pre>Validate.validIndex(myCollection, 2, "The collection index is invalid: ");</pre>
+ *
+ * <p>If the collection is {@code null}, then the message of the
+ * exception is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int)
+ *
+ * @since 3.0
+ */
+ public static <T extends Collection<?>> T validIndex(T collection, int index, String message, Object... values) {
+ Validate.notNull(collection);
+ if (index < 0 || index >= collection.size()) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return collection;
+ }
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * collection; otherwise throwing an exception.</p>
+ *
+ * <pre>Validate.validIndex(myCollection, 2);</pre>
+ *
+ * <p>If the index is invalid, then the message of the exception
+ * is &quot;The validated collection index is invalid: &quot;
+ * followed by the index.</p>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated collection (never {@code null} for method chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T extends Collection<?>> T validIndex(T collection, int index) {
+ return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validIndex string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * character sequence; otherwise throwing an exception with the
+ * specified message.</p>
+ *
+ * <pre>Validate.validIndex(myStr, 2, "The string index is invalid: ");</pre>
+ *
+ * <p>If the character sequence is {@code null}, then the message
+ * of the exception is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int)
+ *
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T validIndex(T chars, int index, String message, Object... values) {
+ Validate.notNull(chars);
+ if (index < 0 || index >= chars.length()) {
+ throw new IndexOutOfBoundsException(String.format(message, values));
+ }
+ return chars;
+ }
+
+ /**
+ * <p>Validates that the index is within the bounds of the argument
+ * character sequence; otherwise throwing an exception.</p>
+ *
+ * <pre>Validate.validIndex(myStr, 2);</pre>
+ *
+ * <p>If the character sequence is {@code null}, then the message
+ * of the exception is &quot;The validated object is
+ * null&quot;.</p>
+ *
+ * <p>If the index is invalid, then the message of the exception
+ * is &quot;The validated character sequence index is invalid: &quot;
+ * followed by the index.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T validIndex(T chars, int index) {
+ return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index));
+ }
+
+ // validState
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the stateful condition is {@code true}; otherwise
+ * throwing an exception. This method is useful when validating according
+ * to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>
+ * Validate.validState(field > 0);
+ * Validate.validState(this.isOk());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated state is
+ * false&quot;.</p>
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void validState(boolean expression) {
+ if (expression == false) {
+ throw new IllegalStateException(DEFAULT_VALID_STATE_EX_MESSAGE);
+ }
+ }
+
+ /**
+ * <p>Validate that the stateful condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.</p>
+ *
+ * <pre>Validate.validState(this.isOk(), "The state is not OK: %s", myObject);</pre>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean)
+ *
+ * @since 3.0
+ */
+ public static void validState(boolean expression, String message, Object... values) {
+ if (expression == false) {
+ throw new IllegalStateException(String.format(message, values));
+ }
+ }
+
+ // matchesPattern
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument character sequence matches the specified regular
+ * expression pattern; otherwise throwing an exception.</p>
+ *
+ * <pre>Validate.matchesPattern("hi", "[a-z]*");</pre>
+ *
+ * <p>The syntax of the pattern is the one used in the {@link Pattern} class.</p>
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void matchesPattern(CharSequence input, String pattern) {
+ if (Pattern.matches(pattern, input) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_MATCHES_PATTERN_EX, input, pattern));
+ }
+ }
+
+ /**
+ * <p>Validate that the specified argument character sequence matches the specified regular
+ * expression pattern; otherwise throwing an exception with the specified message.</p>
+ *
+ * <pre>Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");</pre>
+ *
+ * <p>The syntax of the pattern is the one used in the {@link Pattern} class.</p>
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String)
+ *
+ * @since 3.0
+ */
+ public static void matchesPattern(CharSequence input, String pattern, String message, Object... values) {
+ if (Pattern.matches(pattern, input) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // inclusiveBetween
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument object fall between the two
+ * inclusive values specified; otherwise, throws an exception.</p>
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1);</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T> void inclusiveBetween(T start, T end, Comparable<T> value) {
+ if (value.compareTo(start) < 0 || value.compareTo(end) > 0) {
+ throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end));
+ }
+ }
+
+ /**
+ * <p>Validate that the specified argument object fall between the two
+ * inclusive values specified; otherwise, throws an exception with the
+ * specified message.</p>
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable)
+ *
+ * @since 3.0
+ */
+ public static <T> void inclusiveBetween(T start, T end, Comparable<T> value, String message, Object... values) {
+ if (value.compareTo(start) < 0 || value.compareTo(end) > 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // exclusiveBetween
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception.</p>
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1);</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static <T> void exclusiveBetween(T start, T end, Comparable<T> value) {
+ if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) {
+ throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end));
+ }
+ }
+
+ /**
+ * <p>Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception with the
+ * specified message.</p>
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls out of the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable)
+ *
+ * @since 3.0
+ */
+ public static <T> void exclusiveBetween(T start, T end, Comparable<T> value, String message, Object... values) {
+ if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // isInstanceOf
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the argument is an instance of the specified class; otherwise
+ * throwing an exception. This method is useful when validating according to an arbitrary
+ * class</p>
+ *
+ * <pre>Validate.isInstanceOf(OkClass.class, object);</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object is not an instance of&quot;
+ * followed by the name of the class</p>
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void isInstanceOf(Class<?> type, Object obj) {
+ if (type.isInstance(obj) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName()));
+ }
+ }
+
+ /**
+ * <p>Validate that the argument is an instance of the specified class; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary class</p>
+ *
+ * <pre>Validate.isInstanceOf(OkClass.classs, object, "Wrong class, object is of class %s",
+ * object.getClass().getName());</pre>
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object)
+ *
+ * @since 3.0
+ */
+ public static void isInstanceOf(Class<?> type, Object obj, String message, Object... values) {
+ if (type.isInstance(obj) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+
+ // isAssignableFrom
+ //---------------------------------------------------------------------------------
+
+ /**
+ * <p>Validate that the argument can be converted to the specified class; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating if there will be no casting errors.</p>
+ *
+ * <pre>Validate.isAssignableFrom(SuperClass.class, object.getClass());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object can not be converted to the&quot;
+ * followed by the name of the class and &quot;class&quot;</p>
+ *
+ * @param superType the class the class must be validated against, not null
+ * @param type the class to check, not null
+ * @throws IllegalArgumentException if argument can not be converted to the specified class
+ * @see #isAssignableFrom(Class, Class, String, Object...)
+ *
+ * @since 3.0
+ */
+ public static void isAssignableFrom(Class<?> superType, Class<?> type) {
+ if (superType.isAssignableFrom(type) == false) {
+ throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, superType.getName()));
+ }
+ }
+
+ /**
+ * <p>Validate that the argument can be converted to the specified class; otherwise
+ * throwing an exception. This method is useful when validating if there will be no
+ * casting errors.</p>
+ *
+ * <pre>Validate.isAssignableFrom(SuperClass.class, object.getClass());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object can not be converted to the&quot;
+ * followed by the name of the class and &quot;class&quot;</p>
+ *
+ * @param superType the class the class must be validated against, not null
+ * @param type the class to check, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument can not be converted to the specified class
+ * @see #isAssignableFrom(Class, Class)
+ */
+ public static void isAssignableFrom(Class<?> superType, Class<?> type, String message, Object... values) {
+ if (superType.isAssignableFrom(type) == false) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ }
+}
diff --git a/src/org/apache/commons/lang3/builder/Builder.java b/src/org/apache/commons/lang3/builder/Builder.java
new file mode 100644
index 0000000..ce696e8
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/Builder.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+/**
+ * <p>
+ * The Builder interface is designed to designate a class as a <em>builder</em>
+ * object in the Builder design pattern. Builders are capable of creating and
+ * configuring objects or results that normally take multiple steps to construct
+ * or are very complex to derive.
+ * </p>
+ *
+ * <p>
+ * The builder interface defines a single method, {@link #build()}, that
+ * classes must implement. The result of this method should be the final
+ * configured object or result after all building operations are performed.
+ * </p>
+ *
+ * <p>
+ * It is a recommended practice that the methods supplied to configure the
+ * object or result being built return a reference to {@code this} so that
+ * method calls can be chained together.
+ * </p>
+ *
+ * <p>
+ * Example Builder:
+ * <code><pre>
+ * class FontBuilder implements Builder&lt;Font&gt; {
+ * private Font font;
+ *
+ * public FontBuilder(String fontName) {
+ * this.font = new Font(fontName, Font.PLAIN, 12);
+ * }
+ *
+ * public FontBuilder bold() {
+ * this.font = this.font.deriveFont(Font.BOLD);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * public FontBuilder size(float pointSize) {
+ * this.font = this.font.deriveFont(pointSize);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * // Other Font construction methods
+ *
+ * public Font build() {
+ * return this.font;
+ * }
+ * }
+ * </pre></code>
+ *
+ * Example Builder Usage:
+ * <code><pre>
+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ * .size(14.0f)
+ * .build();
+ * </pre></code>
+ * </p>
+ *
+ * @param <T> the type of object that the builder will construct or compute.
+ *
+ * @since 3.0
+ * @version $Id: Builder.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public interface Builder<T> {
+
+ /**
+ * Returns a reference to the object being constructed or result being
+ * calculated by the builder.
+ *
+ * @return the object constructed or result calculated by the builder.
+ */
+ public T build();
+}
diff --git a/src/org/apache/commons/lang3/builder/CompareToBuilder.java b/src/org/apache/commons/lang3/builder/CompareToBuilder.java
new file mode 100644
index 0000000..6f51a2f
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/CompareToBuilder.java
@@ -0,0 +1,1019 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Comparator;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Assists in implementing {@link java.lang.Comparable#compareTo(Object)} methods.
+ *
+ * It is consistent with <code>equals(Object)</code> and
+ * <code>hashcode()</code> built with {@link EqualsBuilder} and
+ * {@link HashCodeBuilder}.</p>
+ *
+ * <p>Two Objects that compare equal using <code>equals(Object)</code> should normally
+ * also compare equal using <code>compareTo(Object)</code>.</p>
+ *
+ * <p>All relevant fields should be included in the calculation of the
+ * comparison. Derived fields may be ignored. The same fields, in the same
+ * order, should be used in both <code>compareTo(Object)</code> and
+ * <code>equals(Object)</code>.</p>
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class MyClass {
+ * String field1;
+ * int field2;
+ * boolean field3;
+ *
+ * ...
+ *
+ * public int compareTo(Object o) {
+ * MyClass myClass = (MyClass) o;
+ * return new CompareToBuilder()
+ * .appendSuper(super.compareTo(o)
+ * .append(this.field1, myClass.field1)
+ * .append(this.field2, myClass.field2)
+ * .append(this.field3, myClass.field3)
+ * .toComparison();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Alternatively, there are {@link #reflectionCompare(Object, Object) reflectionCompare} methods that use
+ * reflection to determine the fields to append. Because fields can be private,
+ * <code>reflectionCompare</code> uses {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} to
+ * bypass normal access control checks. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than appending explicitly.</p>
+ *
+ * <p>A typical implementation of <code>compareTo(Object)</code> using
+ * <code>reflectionCompare</code> looks like:</p>
+
+ * <pre>
+ * public int compareTo(Object o) {
+ * return CompareToBuilder.reflectionCompare(this, o);
+ * }
+ * </pre>
+ *
+ * @see java.lang.Comparable
+ * @see java.lang.Object#equals(Object)
+ * @see java.lang.Object#hashCode()
+ * @see EqualsBuilder
+ * @see HashCodeBuilder
+ * @since 1.0
+ * @version $Id: CompareToBuilder.java 1090813 2011-04-10 15:03:23Z mbenson $
+ */
+public class CompareToBuilder implements Builder<Integer> {
+
+ /**
+ * Current state of the comparison as appended fields are checked.
+ */
+ private int comparison;
+
+ /**
+ * <p>Constructor for CompareToBuilder.</p>
+ *
+ * <p>Starts off assuming that the objects are equal. Multiple calls are
+ * then made to the various append methods, followed by a call to
+ * {@link #toComparison} to get the result.</p>
+ */
+ public CompareToBuilder() {
+ super();
+ comparison = 0;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares two <code>Object</code>s via reflection.</p>
+ *
+ * <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code>
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>Transient members will be not be compared, as they are likely derived
+ * fields</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>,
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return a negative integer, zero, or a positive integer as <code>lhs</code>
+ * is less than, equal to, or greater than <code>rhs</code>
+ * @throws NullPointerException if either (but not both) parameters are
+ * <code>null</code>
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ */
+ public static int reflectionCompare(Object lhs, Object rhs) {
+ return reflectionCompare(lhs, rhs, false, null);
+ }
+
+ /**
+ * <p>Compares two <code>Object</code>s via reflection.</p>
+ *
+ * <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code>
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If <code>compareTransients</code> is <code>true</code>,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>,
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @return a negative integer, zero, or a positive integer as <code>lhs</code>
+ * is less than, equal to, or greater than <code>rhs</code>
+ * @throws NullPointerException if either <code>lhs</code> or <code>rhs</code>
+ * (but not both) is <code>null</code>
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, boolean compareTransients) {
+ return reflectionCompare(lhs, rhs, compareTransients, null);
+ }
+
+ /**
+ * <p>Compares two <code>Object</code>s via reflection.</p>
+ *
+ * <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code>
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If <code>compareTransients</code> is <code>true</code>,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>,
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields Collection of String fields to exclude
+ * @return a negative integer, zero, or a positive integer as <code>lhs</code>
+ * is less than, equal to, or greater than <code>rhs</code>
+ * @throws NullPointerException if either <code>lhs</code> or <code>rhs</code>
+ * (but not both) is <code>null</code>
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ * @since 2.2
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, Collection<String> excludeFields) {
+ return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * <p>Compares two <code>Object</code>s via reflection.</p>
+ *
+ * <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code>
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If <code>compareTransients</code> is <code>true</code>,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>,
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields array of fields to exclude
+ * @return a negative integer, zero, or a positive integer as <code>lhs</code>
+ * is less than, equal to, or greater than <code>rhs</code>
+ * @throws NullPointerException if either <code>lhs</code> or <code>rhs</code>
+ * (but not both) is <code>null</code>
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ * @since 2.2
+ */
+ public static int reflectionCompare(Object lhs, Object rhs, String... excludeFields) {
+ return reflectionCompare(lhs, rhs, false, null, excludeFields);
+ }
+
+ /**
+ * <p>Compares two <code>Object</code>s via reflection.</p>
+ *
+ * <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code>
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If the <code>compareTransients</code> is <code>true</code>,
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Compares superclass fields up to and including <code>reflectUpToClass</code>.
+ * If <code>reflectUpToClass</code> is <code>null</code>, compares all superclass fields.</li>
+ * </ul>
+ *
+ * <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>,
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @param reflectUpToClass last superclass for which fields are compared
+ * @param excludeFields fields to exclude
+ * @return a negative integer, zero, or a positive integer as <code>lhs</code>
+ * is less than, equal to, or greater than <code>rhs</code>
+ * @throws NullPointerException if either <code>lhs</code> or <code>rhs</code>
+ * (but not both) is <code>null</code>
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ * @since 2.2 (2.0 as <code>reflectionCompare(Object, Object, boolean, Class)</code>)
+ */
+ public static int reflectionCompare(
+ Object lhs,
+ Object rhs,
+ boolean compareTransients,
+ Class<?> reflectUpToClass,
+ String... excludeFields) {
+
+ if (lhs == rhs) {
+ return 0;
+ }
+ if (lhs == null || rhs == null) {
+ throw new NullPointerException();
+ }
+ Class<?> lhsClazz = lhs.getClass();
+ if (!lhsClazz.isInstance(rhs)) {
+ throw new ClassCastException();
+ }
+ CompareToBuilder compareToBuilder = new CompareToBuilder();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
+ lhsClazz = lhsClazz.getSuperclass();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ }
+ return compareToBuilder.toComparison();
+ }
+
+ /**
+ * <p>Appends to <code>builder</code> the comparison of <code>lhs</code>
+ * to <code>rhs</code> using the fields defined in <code>clazz</code>.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param clazz <code>Class</code> that defines fields to be compared
+ * @param builder <code>CompareToBuilder</code> to append to
+ * @param useTransients whether to compare transient fields
+ * @param excludeFields fields to exclude
+ */
+ private static void reflectionAppend(
+ Object lhs,
+ Object rhs,
+ Class<?> clazz,
+ CompareToBuilder builder,
+ boolean useTransients,
+ String[] excludeFields) {
+
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
+ Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && (f.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(f.getModifiers()))
+ && (!Modifier.isStatic(f.getModifiers()))) {
+ try {
+ builder.append(f.get(lhs), f.get(rhs));
+ } catch (IllegalAccessException e) {
+ // This can't happen. Would get a Security exception instead.
+ // Throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Appends to the <code>builder</code> the <code>compareTo(Object)</code>
+ * result of the superclass.</p>
+ *
+ * @param superCompareTo result of calling <code>super.compareTo(Object)</code>
+ * @return this - used to chain append calls
+ * @since 2.0
+ */
+ public CompareToBuilder appendSuper(int superCompareTo) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = superCompareTo;
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Appends to the <code>builder</code> the comparison of
+ * two <code>Object</code>s.</p>
+ *
+ * <ol>
+ * <li>Check if <code>lhs == rhs</code></li>
+ * <li>Check if either <code>lhs</code> or <code>rhs</code> is <code>null</code>,
+ * a <code>null</code> object is less than a non-<code>null</code> object</li>
+ * <li>Check the object contents</li>
+ * </ol>
+ *
+ * <p><code>lhs</code> must either be an array or implement {@link Comparable}.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return this - used to chain append calls
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ */
+ public CompareToBuilder append(Object lhs, Object rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the comparison of
+ * two <code>Object</code>s.</p>
+ *
+ * <ol>
+ * <li>Check if <code>lhs == rhs</code></li>
+ * <li>Check if either <code>lhs</code> or <code>rhs</code> is <code>null</code>,
+ * a <code>null</code> object is less than a non-<code>null</code> object</li>
+ * <li>Check the object contents</li>
+ * </ol>
+ *
+ * <p>If <code>lhs</code> is an array, array comparison methods will be used.
+ * Otherwise <code>comparator</code> will be used to compare the objects.
+ * If <code>comparator</code> is <code>null</code>, <code>lhs</code> must
+ * implement {@link Comparable} instead.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param comparator <code>Comparator</code> used to compare the objects,
+ * <code>null</code> means treat lhs as <code>Comparable</code>
+ * @return this - used to chain append calls
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ * @since 2.0
+ */
+ public CompareToBuilder append(Object lhs, Object rhs, Comparator<?> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.getClass().isArray()) {
+ // switch on type of array, to dispatch to the correct handler
+ // handles multi dimensional arrays
+ // throws a ClassCastException if rhs is not the correct array type
+ if (lhs instanceof long[]) {
+ append((long[]) lhs, (long[]) rhs);
+ } else if (lhs instanceof int[]) {
+ append((int[]) lhs, (int[]) rhs);
+ } else if (lhs instanceof short[]) {
+ append((short[]) lhs, (short[]) rhs);
+ } else if (lhs instanceof char[]) {
+ append((char[]) lhs, (char[]) rhs);
+ } else if (lhs instanceof byte[]) {
+ append((byte[]) lhs, (byte[]) rhs);
+ } else if (lhs instanceof double[]) {
+ append((double[]) lhs, (double[]) rhs);
+ } else if (lhs instanceof float[]) {
+ append((float[]) lhs, (float[]) rhs);
+ } else if (lhs instanceof boolean[]) {
+ append((boolean[]) lhs, (boolean[]) rhs);
+ } else {
+ // not an array of primitives
+ // throws a ClassCastException if rhs is not an array
+ append((Object[]) lhs, (Object[]) rhs, comparator);
+ }
+ } else {
+ // the simple case, not an array, just test the element
+ if (comparator == null) {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparable<Object> comparable = (Comparable<Object>) lhs;
+ comparison = comparable.compareTo(rhs);
+ } else {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparator<Object> comparator2 = (Comparator<Object>) comparator;
+ comparison = comparator2.compare(lhs, rhs);
+ }
+ }
+ return this;
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>long</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(long lhs, long rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>int</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(int lhs, int rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>short</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(short lhs, short rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>char</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(char lhs, char rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>byte</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(byte lhs, byte rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the comparison of
+ * two <code>double</code>s.</p>
+ *
+ * <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * <code>HashCodeBuilder</code>.</p>
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(double lhs, double rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Double.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the comparison of
+ * two <code>float</code>s.</p>
+ *
+ * <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * <code>HashCodeBuilder</code>.</p>
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(float lhs, float rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Float.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the <code>builder</code> the comparison of
+ * two <code>booleans</code>s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(boolean lhs, boolean rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == false) {
+ comparison = -1;
+ } else {
+ comparison = +1;
+ }
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>Object</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a short length array is less than a long length array</li>
+ * <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li>
+ * </ol>
+ *
+ * <p>This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.</p>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ */
+ public CompareToBuilder append(Object[] lhs, Object[] rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>Object</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a short length array is less than a long length array</li>
+ * <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li>
+ * </ol>
+ *
+ * <p>This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.</p>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @param comparator <code>Comparator</code> to use to compare the array elements,
+ * <code>null</code> means to treat <code>lhs</code> elements as <code>Comparable</code>.
+ * @return this - used to chain append calls
+ * @throws ClassCastException if <code>rhs</code> is not assignment-compatible
+ * with <code>lhs</code>
+ * @since 2.0
+ */
+ public CompareToBuilder append(Object[] lhs, Object[] rhs, Comparator<?> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i], comparator);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>long</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(long, long)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(long[] lhs, long[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>int</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(int, int)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(int[] lhs, int[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>short</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(short, short)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(short[] lhs, short[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>char</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(char, char)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(char[] lhs, char[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>byte</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(byte, byte)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(byte[] lhs, byte[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>double</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(double, double)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(double[] lhs, double[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>float</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(float, float)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(float[] lhs, float[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Appends to the <code>builder</code> the deep comparison of
+ * two <code>boolean</code> arrays.</p>
+ *
+ * <ol>
+ * <li>Check if arrays are the same using <code>==</code></li>
+ * <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(boolean, boolean)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(boolean[] lhs, boolean[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = +1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = (lhs.length < rhs.length) ? -1 : +1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a negative integer, a positive integer, or zero as
+ * the <code>builder</code> has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result
+ */
+ public int toComparison() {
+ return comparison;
+ }
+
+ /**
+ * Returns a negative integer, a positive integer, or zero as
+ * the <code>builder</code> has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result
+ *
+ * @since 3.0
+ */
+ public Integer build() {
+ return Integer.valueOf(toComparison());
+ }
+}
+
diff --git a/src/org/apache/commons/lang3/builder/EqualsBuilder.java b/src/org/apache/commons/lang3/builder/EqualsBuilder.java
new file mode 100644
index 0000000..d619495
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/EqualsBuilder.java
@@ -0,0 +1,944 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * <p>Assists in implementing {@link Object#equals(Object)} methods.</p>
+ *
+ * <p> This class provides methods to build a good equals method for any
+ * class. It follows rules laid out in
+ * <a href="http://java.sun.com/docs/books/effective/index.html">Effective Java</a>
+ * , by Joshua Bloch. In particular the rule for comparing <code>doubles</code>,
+ * <code>floats</code>, and arrays can be tricky. Also, making sure that
+ * <code>equals()</code> and <code>hashCode()</code> are consistent can be
+ * difficult.</p>
+ *
+ * <p>Two Objects that compare as equals must generate the same hash code,
+ * but two Objects with the same hash code do not have to be equal.</p>
+ *
+ * <p>All relevant fields should be included in the calculation of equals.
+ * Derived fields may be ignored. In particular, any field used in
+ * generating a hash code must be used in the equals method, and vice
+ * versa.</p>
+ *
+ * <p>Typical use for the code is as follows:</p>
+ * <pre>
+ * public boolean equals(Object obj) {
+ * if (obj == null) { return false; }
+ * if (obj == this) { return true; }
+ * if (obj.getClass() != getClass()) {
+ * return false;
+ * }
+ * MyClass rhs = (MyClass) obj;
+ * return new EqualsBuilder()
+ * .appendSuper(super.equals(obj))
+ * .append(field1, rhs.field1)
+ * .append(field2, rhs.field2)
+ * .append(field3, rhs.field3)
+ * .isEquals();
+ * }
+ * </pre>
+ *
+ * <p> Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * <code>reflectionEquals</code>, uses <code>AccessibleObject.setAccessible</code> to
+ * change the visibility of the fields. This will fail under a security
+ * manager, unless the appropriate permissions are set up correctly. It is
+ * also slower than testing explicitly.</p>
+ *
+ * <p> A typical invocation for this method would look like:</p>
+ * <pre>
+ * public boolean equals(Object obj) {
+ * return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ * </pre>
+ *
+ * @since 1.0
+ * @version $Id: EqualsBuilder.java 1091531 2011-04-12 18:29:49Z ggregory $
+ */
+public class EqualsBuilder implements Builder<Boolean> {
+
+ /**
+ * <p>
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ * </p>
+ *
+ * @since 3.0
+ */
+ private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<Set<Pair<IDKey, IDKey>>>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hashcode really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.indentityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ * <p>
+ * Returns the registry of object pairs being traversed by the reflection
+ * methods in the current thread.
+ * </p>
+ *
+ * @return Set the registry of objects being traversed
+ * @since 3.0
+ */
+ static Set<Pair<IDKey, IDKey>> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * <p>
+ * Converters value pair into a register pair.
+ * </p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ *
+ * @return the pair
+ */
+ static Pair<IDKey, IDKey> getRegisterPair(Object lhs, Object rhs) {
+ IDKey left = new IDKey(lhs);
+ IDKey right = new IDKey(rhs);
+ return Pair.of(left, right);
+ }
+
+ /**
+ * <p>
+ * Returns <code>true</code> if the registry contains the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ * Objects might be swapped therefore a check is needed if the object pair
+ * is registered in given or swapped order.
+ * </p>
+ *
+ * @param lhs <code>this</code> object to lookup in registry
+ * @param rhs the other object to lookup on registry
+ * @return boolean <code>true</code> if the registry contains the given object.
+ * @since 3.0
+ */
+ static boolean isRegistered(Object lhs, Object rhs) {
+ Set<Pair<IDKey, IDKey>> registry = getRegistry();
+ Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
+ Pair<IDKey, IDKey> swappedPair = Pair.of(pair.getLeft(), pair.getRight());
+
+ return registry != null
+ && (registry.contains(pair) || registry.contains(swappedPair));
+ }
+
+ /**
+ * <p>
+ * Registers the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ * </p>
+ *
+ * @param lhs <code>this</code> object to register
+ * @param rhs the other object to register
+ */
+ static void register(Object lhs, Object rhs) {
+ synchronized (EqualsBuilder.class) {
+ if (getRegistry() == null) {
+ REGISTRY.set(new HashSet<Pair<IDKey, IDKey>>());
+ }
+ }
+
+ Set<Pair<IDKey, IDKey>> registry = getRegistry();
+ Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
+ registry.add(pair);
+ }
+
+ /**
+ * <p>
+ * Unregisters the given object pair.
+ * </p>
+ *
+ * <p>
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param lhs <code>this</code> object to unregister
+ * @param rhs the other object to unregister
+ * @since 3.0
+ */
+ static void unregister(Object lhs, Object rhs) {
+ Set<Pair<IDKey, IDKey>> registry = getRegistry();
+ if (registry != null) {
+ Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
+ registry.remove(pair);
+ synchronized (EqualsBuilder.class) {
+ //read again
+ registry = getRegistry();
+ if (registry != null && registry.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * If the fields tested are equals.
+ * The default value is <code>true</code>.
+ */
+ private boolean isEquals = true;
+
+ /**
+ * <p>Constructor for EqualsBuilder.</p>
+ *
+ * <p>Starts off assuming that equals is <code>true</code>.</p>
+ * @see Object#equals(Object)
+ */
+ public EqualsBuilder() {
+ // do nothing for now.
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param excludeFields Collection of String field names to exclude from testing
+ * @return <code>true</code> if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, Collection<String> excludeFields) {
+ return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param excludeFields array of field names to exclude from testing
+ * @return <code>true</code> if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, String... excludeFields) {
+ return reflectionEquals(lhs, rhs, false, null, excludeFields);
+ }
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>If the TestTransients parameter is set to <code>true</code>, transient
+ * members will be tested, otherwise they are ignored, as they are likely
+ * derived fields, and not part of the value of the <code>Object</code>.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @return <code>true</code> if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) {
+ return reflectionEquals(lhs, rhs, testTransients, null);
+ }
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>If the testTransients parameter is set to <code>true</code>, transient
+ * members will be tested, otherwise they are ignored, as they are likely
+ * derived fields, and not part of the value of the <code>Object</code>.</p>
+ *
+ * <p>Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive),
+ * may be <code>null</code>
+ * @param excludeFields array of field names to exclude from testing
+ * @return <code>true</code> if the two Objects have tested equals.
+ * @since 2.0
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class<?> reflectUpToClass,
+ String... excludeFields) {
+ if (lhs == rhs) {
+ return true;
+ }
+ if (lhs == null || rhs == null) {
+ return false;
+ }
+ // Find the leaf class since there may be transients in the leaf
+ // class or in classes between the leaf and root.
+ // If we are not testing transients or a subclass has no ivars,
+ // then a subclass can test equals to a superclass.
+ Class<?> lhsClass = lhs.getClass();
+ Class<?> rhsClass = rhs.getClass();
+ Class<?> testClass;
+ if (lhsClass.isInstance(rhs)) {
+ testClass = lhsClass;
+ if (!rhsClass.isInstance(lhs)) {
+ // rhsClass is a subclass of lhsClass
+ testClass = rhsClass;
+ }
+ } else if (rhsClass.isInstance(lhs)) {
+ testClass = rhsClass;
+ if (!lhsClass.isInstance(rhs)) {
+ // lhsClass is a subclass of rhsClass
+ testClass = lhsClass;
+ }
+ } else {
+ // The two classes are not related.
+ return false;
+ }
+ EqualsBuilder equalsBuilder = new EqualsBuilder();
+ try {
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
+ while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
+ testClass = testClass.getSuperclass();
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
+ }
+ } catch (IllegalArgumentException e) {
+ // In this case, we tried to test a subclass vs. a superclass and
+ // the subclass has ivars or the ivars are transient and
+ // we are testing transients.
+ // If a subclass has ivars that we are trying to test them, we get an
+ // exception and we know that the objects are not equal.
+ return false;
+ }
+ return equalsBuilder.isEquals();
+ }
+
+ /**
+ * <p>Appends the fields and values defined by the given object of the
+ * given Class.</p>
+ *
+ * @param lhs the left hand object
+ * @param rhs the right hand object
+ * @param clazz the class to append details of
+ * @param builder the builder to append to
+ * @param useTransients whether to test transient fields
+ * @param excludeFields array of field names to exclude from testing
+ */
+ private static void reflectionAppend(
+ Object lhs,
+ Object rhs,
+ Class<?> clazz,
+ EqualsBuilder builder,
+ boolean useTransients,
+ String[] excludeFields) {
+
+ if (isRegistered(lhs, rhs)) {
+ return;
+ }
+
+ try {
+ register(lhs, rhs);
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && builder.isEquals; i++) {
+ Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && (f.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(f.getModifiers()))
+ && (!Modifier.isStatic(f.getModifiers()))) {
+ try {
+ builder.append(f.get(lhs), f.get(rhs));
+ } catch (IllegalAccessException e) {
+ //this can't happen. Would get a Security exception instead
+ //throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(lhs, rhs);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * <p>Adds the result of <code>super.equals()</code> to this builder.</p>
+ *
+ * @param superEquals the result of calling <code>super.equals()</code>
+ * @return EqualsBuilder - used to chain calls.
+ * @since 2.0
+ */
+ public EqualsBuilder appendSuper(boolean superEquals) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = superEquals;
+ return this;
+ }
+
+ //-------------------------------------------------------------------------
+
+ /**
+ * <p>Test if two <code>Object</code>s are equal using their
+ * <code>equals</code> method.</p>
+ *
+ * @param lhs the left hand object
+ * @param rhs the right hand object
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(Object lhs, Object rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ Class<?> lhsClass = lhs.getClass();
+ if (!lhsClass.isArray()) {
+ // The simple case, not an array, just test the element
+ isEquals = lhs.equals(rhs);
+ } else if (lhs.getClass() != rhs.getClass()) {
+ // Here when we compare different dimensions, for example: a boolean[][] to a boolean[]
+ this.setEquals(false);
+ }
+ // 'Switch' on type of array, to dispatch to the correct handler
+ // This handles multi dimensional arrays of the same depth
+ else if (lhs instanceof long[]) {
+ append((long[]) lhs, (long[]) rhs);
+ } else if (lhs instanceof int[]) {
+ append((int[]) lhs, (int[]) rhs);
+ } else if (lhs instanceof short[]) {
+ append((short[]) lhs, (short[]) rhs);
+ } else if (lhs instanceof char[]) {
+ append((char[]) lhs, (char[]) rhs);
+ } else if (lhs instanceof byte[]) {
+ append((byte[]) lhs, (byte[]) rhs);
+ } else if (lhs instanceof double[]) {
+ append((double[]) lhs, (double[]) rhs);
+ } else if (lhs instanceof float[]) {
+ append((float[]) lhs, (float[]) rhs);
+ } else if (lhs instanceof boolean[]) {
+ append((boolean[]) lhs, (boolean[]) rhs);
+ } else {
+ // Not an array of primitives
+ append((Object[]) lhs, (Object[]) rhs);
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Test if two <code>long</code> s are equal.
+ * </p>
+ *
+ * @param lhs
+ * the left hand <code>long</code>
+ * @param rhs
+ * the right hand <code>long</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(long lhs, long rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Test if two <code>int</code>s are equal.</p>
+ *
+ * @param lhs the left hand <code>int</code>
+ * @param rhs the right hand <code>int</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(int lhs, int rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Test if two <code>short</code>s are equal.</p>
+ *
+ * @param lhs the left hand <code>short</code>
+ * @param rhs the right hand <code>short</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(short lhs, short rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Test if two <code>char</code>s are equal.</p>
+ *
+ * @param lhs the left hand <code>char</code>
+ * @param rhs the right hand <code>char</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(char lhs, char rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Test if two <code>byte</code>s are equal.</p>
+ *
+ * @param lhs the left hand <code>byte</code>
+ * @param rhs the right hand <code>byte</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(byte lhs, byte rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Test if two <code>double</code>s are equal by testing that the
+ * pattern of bits returned by <code>doubleToLong</code> are equal.</p>
+ *
+ * <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * <code>HashCodeBuilder</code>.</p>
+ *
+ * @param lhs the left hand <code>double</code>
+ * @param rhs the right hand <code>double</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(double lhs, double rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs));
+ }
+
+ /**
+ * <p>Test if two <code>float</code>s are equal byt testing that the
+ * pattern of bits returned by doubleToLong are equal.</p>
+ *
+ * <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * <code>HashCodeBuilder</code>.</p>
+ *
+ * @param lhs the left hand <code>float</code>
+ * @param rhs the right hand <code>float</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(float lhs, float rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs));
+ }
+
+ /**
+ * <p>Test if two <code>booleans</code>s are equal.</p>
+ *
+ * @param lhs the left hand <code>boolean</code>
+ * @param rhs the right hand <code>boolean</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(boolean lhs, boolean rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ isEquals = (lhs == rhs);
+ return this;
+ }
+
+ /**
+ * <p>Performs a deep comparison of two <code>Object</code> arrays.</p>
+ *
+ * <p>This also will be called for the top level of
+ * multi-dimensional, ragged, and multi-typed arrays.</p>
+ *
+ * @param lhs the left hand <code>Object[]</code>
+ * @param rhs the right hand <code>Object[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(Object[] lhs, Object[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>long</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(long, long)} is used.</p>
+ *
+ * @param lhs the left hand <code>long[]</code>
+ * @param rhs the right hand <code>long[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(long[] lhs, long[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>int</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(int, int)} is used.</p>
+ *
+ * @param lhs the left hand <code>int[]</code>
+ * @param rhs the right hand <code>int[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(int[] lhs, int[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>short</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(short, short)} is used.</p>
+ *
+ * @param lhs the left hand <code>short[]</code>
+ * @param rhs the right hand <code>short[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(short[] lhs, short[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>char</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(char, char)} is used.</p>
+ *
+ * @param lhs the left hand <code>char[]</code>
+ * @param rhs the right hand <code>char[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(char[] lhs, char[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>byte</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(byte, byte)} is used.</p>
+ *
+ * @param lhs the left hand <code>byte[]</code>
+ * @param rhs the right hand <code>byte[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(byte[] lhs, byte[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>double</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(double, double)} is used.</p>
+ *
+ * @param lhs the left hand <code>double[]</code>
+ * @param rhs the right hand <code>double[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(double[] lhs, double[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>float</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(float, float)} is used.</p>
+ *
+ * @param lhs the left hand <code>float[]</code>
+ * @param rhs the right hand <code>float[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(float[] lhs, float[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Deep comparison of array of <code>boolean</code>. Length and all
+ * values are compared.</p>
+ *
+ * <p>The method {@link #append(boolean, boolean)} is used.</p>
+ *
+ * @param lhs the left hand <code>boolean[]</code>
+ * @param rhs the right hand <code>boolean[]</code>
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder append(boolean[] lhs, boolean[] rhs) {
+ if (isEquals == false) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ this.setEquals(false);
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ this.setEquals(false);
+ return this;
+ }
+ for (int i = 0; i < lhs.length && isEquals; ++i) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Returns <code>true</code> if the fields that have been checked
+ * are all equal.</p>
+ *
+ * @return boolean
+ */
+ public boolean isEquals() {
+ return this.isEquals;
+ }
+
+ /**
+ * <p>Returns <code>true</code> if the fields that have been checked
+ * are all equal.</p>
+ *
+ * @return <code>true</code> if all of the fields that have been checked
+ * are equal, <code>false</code> otherwise.
+ *
+ * @since 3.0
+ */
+ public Boolean build() {
+ return Boolean.valueOf(isEquals());
+ }
+
+ /**
+ * Sets the <code>isEquals</code> value.
+ *
+ * @param isEquals The value to set.
+ * @since 2.1
+ */
+ protected void setEquals(boolean isEquals) {
+ this.isEquals = isEquals;
+ }
+
+ /**
+ * Reset the EqualsBuilder so you can use the same object again
+ * @since 2.5
+ */
+ public void reset() {
+ this.isEquals = true;
+ }
+}
diff --git a/src/org/apache/commons/lang3/builder/HashCodeBuilder.java b/src/org/apache/commons/lang3/builder/HashCodeBuilder.java
new file mode 100644
index 0000000..9ae2bd5
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/HashCodeBuilder.java
@@ -0,0 +1,961 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * <p>
+ * Assists in implementing {@link Object#hashCode()} methods.
+ * </p>
+ *
+ * <p>
+ * This class enables a good <code>hashCode</code> method to be built for any class. It follows the rules laid out in
+ * the book <a href="http://java.sun.com/docs/books/effective/index.html">Effective Java</a> by Joshua Bloch. Writing a
+ * good <code>hashCode</code> method is actually quite difficult. This class aims to simplify the process.
+ * </p>
+ *
+ * <p>
+ * The following is the approach taken. When appending a data field, the current total is multiplied by the
+ * multiplier then a relevant value
+ * for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then
+ * appending the integer 45 will create a hashcode of 674, namely 17 * 37 + 45.
+ * </p>
+ *
+ * <p>
+ * All relevant fields from the object should be included in the <code>hashCode</code> method. Derived fields may be
+ * excluded. In general, any field used in the <code>equals</code> method must be used in the <code>hashCode</code>
+ * method.
+ * </p>
+ *
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ *
+ * <pre>
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * ...
+ *
+ * public int hashCode() {
+ * // you pick a hard-coded, randomly chosen, non-zero, odd number
+ * // ideally different for each class
+ * return new HashCodeBuilder(17, 37).
+ * append(name).
+ * append(age).
+ * append(smoker).
+ * toHashCode();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * If required, the superclass <code>hashCode()</code> can be added using {@link #appendSuper}.
+ * </p>
+ *
+ * <p>
+ * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are
+ * usually private, the method, <code>reflectionHashCode</code>, uses <code>AccessibleObject.setAccessible</code>
+ * to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions
+ * are set up correctly. It is also slower than testing explicitly.
+ * </p>
+ *
+ * <p>
+ * A typical invocation for this method would look like:
+ * </p>
+ *
+ * <pre>
+ * public int hashCode() {
+ * return HashCodeBuilder.reflectionHashCode(this);
+ * }
+ * </pre>
+ *
+ * @since 1.0
+ * @version $Id: HashCodeBuilder.java 1144929 2011-07-10 18:26:16Z ggregory $
+ */
+public class HashCodeBuilder implements Builder<Integer> {
+ /**
+ * <p>
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ * </p>
+ *
+ * @since 2.3
+ */
+ private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<Set<IDKey>>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hashcode really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.indentityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ * <p>
+ * Returns the registry of objects being traversed by the reflection methods in the current thread.
+ * </p>
+ *
+ * @return Set the registry of objects being traversed
+ * @since 2.3
+ */
+ static Set<IDKey> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * <p>
+ * Returns <code>true</code> if the registry contains the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean <code>true</code> if the registry contains the given object.
+ * @since 2.3
+ */
+ static boolean isRegistered(Object value) {
+ Set<IDKey> registry = getRegistry();
+ return registry != null && registry.contains(new IDKey(value));
+ }
+
+ /**
+ * <p>
+ * Appends the fields and values defined by the given object of the given <code>Class</code>.
+ * </p>
+ *
+ * @param object
+ * the object to append details of
+ * @param clazz
+ * the class to append details of
+ * @param builder
+ * the builder to append to
+ * @param useTransients
+ * whether to use transient fields
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ */
+ private static void reflectionAppend(Object object, Class<?> clazz, HashCodeBuilder builder, boolean useTransients,
+ String[] excludeFields) {
+ if (isRegistered(object)) {
+ return;
+ }
+ try {
+ register(object);
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (Field field : fields) {
+ if (!ArrayUtils.contains(excludeFields, field.getName())
+ && (field.getName().indexOf('$') == -1)
+ && (useTransients || !Modifier.isTransient(field.getModifiers()))
+ && (!Modifier.isStatic(field.getModifiers()))) {
+ try {
+ Object fieldValue = field.get(object);
+ builder.append(fieldValue);
+ } catch (IllegalAccessException e) {
+ // this can't happen. Would get a Security exception instead
+ // throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(object);
+ }
+ }
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
+ }
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object,
+ boolean testTransients) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
+ }
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be included up to and including the specified
+ * superclass. A null superclass is treated as java.lang.Object.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param <T>
+ * the type of the object involved
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @param testTransients
+ * whether to include transient fields
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be <code>null</code>
+ * @param excludeFields
+ * array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ * @since 2.0
+ */
+ public static <T> int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, T object,
+ boolean testTransients, Class<? super T> reflectUpToClass, String... excludeFields) {
+
+ if (object == null) {
+ throw new IllegalArgumentException("The object to build a hash code for must not be null");
+ }
+ HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
+ Class<?> clazz = object.getClass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
+ clazz = clazz.getSuperclass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ }
+ return builder.toHashCode();
+ }
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <P>
+ * If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is <code>null</code>
+ */
+ public static int reflectionHashCode(Object object, boolean testTransients) {
+ return reflectionHashCode(17, 37, object, testTransients, null);
+ }
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is <code>null</code>
+ */
+ public static int reflectionHashCode(Object object, Collection<String> excludeFields) {
+ return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * <p>
+ * This method uses reflection to build a valid hash code.
+ * </p>
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * <code>Object</code>.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * @param object
+ * the Object to create a <code>hashCode</code> for
+ * @param excludeFields
+ * array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is <code>null</code>
+ */
+ public static int reflectionHashCode(Object object, String... excludeFields) {
+ return reflectionHashCode(17, 37, object, false, null, excludeFields);
+ }
+
+ /**
+ * <p>
+ * Registers the given object. Used by the reflection methods to avoid infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to register.
+ */
+ static void register(Object value) {
+ synchronized (HashCodeBuilder.class) {
+ if (getRegistry() == null) {
+ REGISTRY.set(new HashSet<IDKey>());
+ }
+ }
+ getRegistry().add(new IDKey(value));
+ }
+
+ /**
+ * <p>
+ * Unregisters the given object.
+ * </p>
+ *
+ * <p>
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value
+ * The object to unregister.
+ * @since 2.3
+ */
+ static void unregister(Object value) {
+ Set<IDKey> registry = getRegistry();
+ if (registry != null) {
+ registry.remove(new IDKey(value));
+ synchronized (HashCodeBuilder.class) {
+ //read again
+ registry = getRegistry();
+ if (registry != null && registry.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Constant to use in building the hashCode.
+ */
+ private final int iConstant;
+
+ /**
+ * Running total of the hashCode.
+ */
+ private int iTotal = 0;
+
+ /**
+ * <p>
+ * Uses two hard coded choices for the constants needed to build a <code>hashCode</code>.
+ * </p>
+ */
+ public HashCodeBuilder() {
+ iConstant = 37;
+ iTotal = 17;
+ }
+
+ /**
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital.
+ * </p>
+ *
+ * <p>
+ * Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ */
+ public HashCodeBuilder(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber) {
+ if (initialNonZeroOddNumber == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires a non zero initial value");
+ }
+ if (initialNonZeroOddNumber % 2 == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires an odd initial value");
+ }
+ if (multiplierNonZeroOddNumber == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires a non zero multiplier");
+ }
+ if (multiplierNonZeroOddNumber % 2 == 0) {
+ throw new IllegalArgumentException("HashCodeBuilder requires an odd multiplier");
+ }
+ iConstant = multiplierNonZeroOddNumber;
+ iTotal = initialNonZeroOddNumber;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>boolean</code>.
+ * </p>
+ * <p>
+ * This adds <code>1</code> when true, and <code>0</code> when false to the <code>hashCode</code>.
+ * </p>
+ * <p>
+ * This is in contrast to the standard <code>java.lang.Boolean.hashCode</code> handling, which computes
+ * a <code>hashCode</code> value of <code>1231</code> for <code>java.lang.Boolean</code> instances
+ * that represent <code>true</code> or <code>1237</code> for <code>java.lang.Boolean</code> instances
+ * that represent <code>false</code>.
+ * </p>
+ * <p>
+ * This is in accordance with the <quote>Effective Java</quote> design.
+ * </p>
+ *
+ * @param value
+ * the boolean to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(boolean value) {
+ iTotal = iTotal * iConstant + (value ? 0 : 1);
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>boolean</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(boolean[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (boolean element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>byte</code>.
+ * </p>
+ *
+ * @param value
+ * the byte to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(byte value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>byte</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(byte[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (byte element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>char</code>.
+ * </p>
+ *
+ * @param value
+ * the char to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(char value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>char</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(char[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (char element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>double</code>.
+ * </p>
+ *
+ * @param value
+ * the double to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(double value) {
+ return append(Double.doubleToLongBits(value));
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>double</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(double[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (double element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>float</code>.
+ * </p>
+ *
+ * @param value
+ * the float to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(float value) {
+ iTotal = iTotal * iConstant + Float.floatToIntBits(value);
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>float</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(float[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (float element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for an <code>int</code>.
+ * </p>
+ *
+ * @param value
+ * the int to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(int value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for an <code>int</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(int[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (int element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>long</code>.
+ * </p>
+ *
+ * @param value
+ * the long to add to the <code>hashCode</code>
+ * @return this
+ */
+ // NOTE: This method uses >> and not >>> as Effective Java and
+ // Long.hashCode do. Ideally we should switch to >>> at
+ // some stage. There are backwards compat issues, so
+ // that will have to wait for the time being. cf LANG-342.
+ public HashCodeBuilder append(long value) {
+ iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32)));
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>long</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(long[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (long element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for an <code>Object</code>.
+ * </p>
+ *
+ * @param object
+ * the Object to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(Object object) {
+ if (object == null) {
+ iTotal = iTotal * iConstant;
+
+ } else {
+ if(object.getClass().isArray()) {
+ // 'Switch' on type of array, to dispatch to the correct handler
+ // This handles multi dimensional arrays
+ if (object instanceof long[]) {
+ append((long[]) object);
+ } else if (object instanceof int[]) {
+ append((int[]) object);
+ } else if (object instanceof short[]) {
+ append((short[]) object);
+ } else if (object instanceof char[]) {
+ append((char[]) object);
+ } else if (object instanceof byte[]) {
+ append((byte[]) object);
+ } else if (object instanceof double[]) {
+ append((double[]) object);
+ } else if (object instanceof float[]) {
+ append((float[]) object);
+ } else if (object instanceof boolean[]) {
+ append((boolean[]) object);
+ } else {
+ // Not an array of primitives
+ append((Object[]) object);
+ }
+ } else {
+ iTotal = iTotal * iConstant + object.hashCode();
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for an <code>Object</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(Object[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (Object element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>short</code>.
+ * </p>
+ *
+ * @param value
+ * the short to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(short value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * <p>
+ * Append a <code>hashCode</code> for a <code>short</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public HashCodeBuilder append(short[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (short element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Adds the result of super.hashCode() to this builder.
+ * </p>
+ *
+ * @param superHashCode
+ * the result of calling <code>super.hashCode()</code>
+ * @return this HashCodeBuilder, used to chain calls.
+ * @since 2.0
+ */
+ public HashCodeBuilder appendSuper(int superHashCode) {
+ iTotal = iTotal * iConstant + superHashCode;
+ return this;
+ }
+
+ /**
+ * <p>
+ * Return the computed <code>hashCode</code>.
+ * </p>
+ *
+ * @return <code>hashCode</code> based on the fields appended
+ */
+ public int toHashCode() {
+ return iTotal;
+ }
+
+ /**
+ * Returns the computed <code>hashCode</code>.
+ *
+ * @return <code>hashCode</code> based on the fields appended
+ *
+ * @since 3.0
+ */
+ public Integer build() {
+ return Integer.valueOf(toHashCode());
+ }
+
+ /**
+ * <p>
+ * The computed <code>hashCode</code> from toHashCode() is returned due to the likelihood
+ * of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for
+ * HashCodeBuilder itself is.</p>
+ *
+ * @return <code>hashCode</code> based on the fields appended
+ * @since 2.5
+ */
+ @Override
+ public int hashCode() {
+ return toHashCode();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/builder/IDKey.java b/src/org/apache/commons/lang3/builder/IDKey.java
new file mode 100644
index 0000000..68414c0
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/IDKey.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+// adapted from org.apache.axis.utils.IDKey
+
+/**
+ * Wrap an identity key (System.identityHashCode())
+ * so that an object can only be equal() to itself.
+ *
+ * This is necessary to disambiguate the occasional duplicate
+ * identityHashCodes that can occur.
+ *
+ */
+final class IDKey {
+ private final Object value;
+ private final int id;
+
+ /**
+ * Constructor for IDKey
+ * @param _value The value
+ */
+ public IDKey(Object _value) {
+ // This is the Object hashcode
+ id = System.identityHashCode(_value);
+ // There have been some cases (LANG-459) that return the
+ // same identity hash code for different objects. So
+ // the value is also added to disambiguate these cases.
+ value = _value;
+ }
+
+ /**
+ * returns hashcode - i.e. the system identity hashcode.
+ * @return the hashcode
+ */
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ /**
+ * checks if instances are equal
+ * @param other The other object to compare to
+ * @return if the instances are for the same object
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof IDKey)) {
+ return false;
+ }
+ IDKey idKey = (IDKey) other;
+ if (id != idKey.id) {
+ return false;
+ }
+ // Note that identity equals is used.
+ return value == idKey.value;
+ }
+}
diff --git a/src/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/src/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
new file mode 100644
index 0000000..df85657
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * <p>
+ * Assists in implementing {@link Object#toString()} methods using reflection.
+ * </p>
+ *
+ * <p>
+ * This class uses reflection to determine the fields to append. Because these fields are usually private, the class
+ * uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to
+ * change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
+ * set up correctly.
+ * </p>
+ *
+ * <p>
+ * A typical invocation for this method would look like:
+ * </p>
+ *
+ * <pre>
+ * public String toString() {
+ * return ReflectionToStringBuilder.toString(this);
+ * }</pre>
+ *
+ *
+ *
+ * <p>
+ * You can also use the builder to debug 3rd party objects:
+ * </p>
+ *
+ * <pre>
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));</pre>
+ *
+ *
+ *
+ * <p>
+ * A subclass can control field output by overriding the methods:
+ * <ul>
+ * <li>{@link #accept(java.lang.reflect.Field)}</li>
+ * <li>{@link #getValue(java.lang.reflect.Field)}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * For example, this method does <i>not</i> include the <code>password</code> field in the returned
+ * <code>String</code>:
+ * </p>
+ *
+ * <pre>
+ * public String toString() {
+ * return (new ReflectionToStringBuilder(this) {
+ * protected boolean accept(Field f) {
+ * return super.accept(f) && !f.getName().equals("password");
+ * }
+ * }).toString();
+ * }</pre>
+ *
+ *
+ *
+ * <p>
+ * The exact format of the <code>toString</code> is determined by the {@link ToStringStyle} passed into the
+ * constructor.
+ * </p>
+ *
+ * @since 2.0
+ * @version $Id: ReflectionToStringBuilder.java 1090821 2011-04-10 15:59:07Z mbenson $
+ */
+public class ReflectionToStringBuilder extends ToStringBuilder {
+
+ /**
+ * <p>
+ * Builds a <code>toString</code> value using the default <code>ToStringStyle</code> through reflection.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ */
+ public static String toString(Object object) {
+ return toString(object, null, false, false, null);
+ }
+
+ /**
+ * <p>
+ * Builds a <code>toString</code> value through reflection.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object or <code>ToStringStyle</code> is <code>null</code>
+ */
+ public static String toString(Object object, ToStringStyle style) {
+ return toString(object, style, false, false, null);
+ }
+
+ /**
+ * <p>
+ * Builds a <code>toString</code> value through reflection.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * If the <code>outputTransients</code> is <code>true</code>, transient members will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param outputTransients
+ * whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ */
+ public static String toString(Object object, ToStringStyle style, boolean outputTransients) {
+ return toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ * <p>
+ * Builds a <code>toString</code> value through reflection.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
+ * ignored.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ * @since 2.1
+ */
+ public static String toString(Object object, ToStringStyle style, boolean outputTransients, boolean outputStatics) {
+ return toString(object, style, outputTransients, outputStatics, null);
+ }
+
+ /**
+ * <p>
+ * Builds a <code>toString</code> value through reflection.
+ * </p>
+ *
+ * <p>
+ * It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
+ * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.
+ * </p>
+ *
+ * <p>
+ * If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
+ * ignored.
+ * </p>
+ *
+ * <p>
+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
+ * <code>java.lang.Object</code>.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
+ * </p>
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be <code>null</code>
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is <code>null</code>
+ * @since 2.1
+ */
+ public static <T> String toString(
+ T object, ToStringStyle style, boolean outputTransients,
+ boolean outputStatics, Class<? super T> reflectUpToClass) {
+ return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
+ .toString();
+ }
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude. Null excludes nothing.
+ * @return The toString value.
+ */
+ public static String toStringExclude(Object object, Collection<String> excludeFieldNames) {
+ return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
+ }
+
+ /**
+ * Converts the given Collection into an array of Strings. The returned array does not contain <code>null</code>
+ * entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
+ * is <code>null</code>.
+ *
+ * @param collection
+ * The collection to convert
+ * @return A new array of Strings.
+ */
+ static String[] toNoNullStringArray(Collection<String> collection) {
+ if (collection == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ return toNoNullStringArray(collection.toArray());
+ }
+
+ /**
+ * Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
+ * (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
+ * if an array element is <code>null</code>.
+ *
+ * @param array
+ * The array to check
+ * @return The given array or a new array without null.
+ */
+ static String[] toNoNullStringArray(Object[] array) {
+ List<String> list = new ArrayList<String>(array.length);
+ for (Object e : array) {
+ if (e != null) {
+ list.add(e.toString());
+ }
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude
+ * @return The toString value.
+ */
+ public static String toStringExclude(Object object, String... excludeFieldNames) {
+ return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
+ }
+
+ /**
+ * Whether or not to append static fields.
+ */
+ private boolean appendStatics = false;
+
+ /**
+ * Whether or not to append transient fields.
+ */
+ private boolean appendTransients = false;
+
+ /**
+ * Which field names to exclude from output. Intended for fields like <code>"password"</code>.
+ *
+ * @since 3.0 this is protected instead of private
+ */
+ protected String[] excludeFieldNames;
+
+ /**
+ * The last super class to stop appending fields for.
+ */
+ private Class<?> upToClass = null;
+
+ /**
+ * <p>
+ * Constructor.
+ * </p>
+ *
+ * <p>
+ * This constructor outputs using the default style set with <code>setDefaultStyle</code>.
+ * </p>
+ *
+ * @param object
+ * the Object to build a <code>toString</code> for, must not be <code>null</code>
+ * @throws IllegalArgumentException
+ * if the Object passed in is <code>null</code>
+ */
+ public ReflectionToStringBuilder(Object object) {
+ super(object);
+ }
+
+ /**
+ * <p>
+ * Constructor.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default style is used.
+ * </p>
+ *
+ * @param object
+ * the Object to build a <code>toString</code> for, must not be <code>null</code>
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @throws IllegalArgumentException
+ * if the Object passed in is <code>null</code>
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style) {
+ super(object, style);
+ }
+
+ /**
+ * <p>
+ * Constructor.
+ * </p>
+ *
+ * <p>
+ * If the style is <code>null</code>, the default style is used.
+ * </p>
+ *
+ * <p>
+ * If the buffer is <code>null</code>, a new one is created.
+ * </p>
+ *
+ * @param object
+ * the Object to build a <code>toString</code> for
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param buffer
+ * the <code>StringBuffer</code> to populate, may be <code>null</code>
+ * @throws IllegalArgumentException
+ * if the Object passed in is <code>null</code>
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
+ super(object, style, buffer);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to build a <code>toString</code> for
+ * @param style
+ * the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param buffer
+ * the <code>StringBuffer</code> to populate, may be <code>null</code>
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be <code>null</code>
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @since 2.1
+ */
+ public <T> ReflectionToStringBuilder(
+ T object, ToStringStyle style, StringBuffer buffer,
+ Class<? super T> reflectUpToClass, boolean outputTransients, boolean outputStatics) {
+ super(object, style, buffer);
+ this.setUpToClass(reflectUpToClass);
+ this.setAppendTransients(outputTransients);
+ this.setAppendStatics(outputStatics);
+ }
+
+ /**
+ * Returns whether or not to append the given <code>Field</code>.
+ * <ul>
+ * <li>Transient fields are appended only if {@link #isAppendTransients()} returns <code>true</code>.
+ * <li>Static fields are appended only if {@link #isAppendStatics()} returns <code>true</code>.
+ * <li>Inner class fields are not appened.</li>
+ * </ul>
+ *
+ * @param field
+ * The Field to test.
+ * @return Whether or not to append the given <code>Field</code>.
+ */
+ protected boolean accept(Field field) {
+ if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
+ // Reject field from inner class.
+ return false;
+ }
+ if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
+ // Reject transient fields.
+ return false;
+ }
+ if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
+ // Reject static fields.
+ return false;
+ }
+ if (this.excludeFieldNames != null
+ && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
+ // Reject fields from the getExcludeFieldNames list.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <p>
+ * Appends the fields and values defined by the given object of the given Class.
+ * </p>
+ *
+ * <p>
+ * If a cycle is detected as an object is &quot;toString()'ed&quot;, such an object is rendered as if
+ * <code>Object.toString()</code> had been called and not implemented by the object.
+ * </p>
+ *
+ * @param clazz
+ * The class of object parameter
+ */
+ protected void appendFieldsIn(Class<?> clazz) {
+ if (clazz.isArray()) {
+ this.reflectionAppendArray(this.getObject());
+ return;
+ }
+ Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (Field field : fields) {
+ String fieldName = field.getName();
+ if (this.accept(field)) {
+ try {
+ // Warning: Field.get(Object) creates wrappers objects
+ // for primitive types.
+ Object fieldValue = this.getValue(field);
+ this.append(fieldName, fieldValue);
+ } catch (IllegalAccessException ex) {
+ //this can't happen. Would get a Security exception
+ // instead
+ //throw a runtime exception in case the impossible
+ // happens.
+ throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * @return Returns the excludeFieldNames.
+ */
+ public String[] getExcludeFieldNames() {
+ return this.excludeFieldNames.clone();
+ }
+
+ /**
+ * <p>
+ * Gets the last super class to stop appending fields for.
+ * </p>
+ *
+ * @return The last super class to stop appending fields for.
+ */
+ public Class<?> getUpToClass() {
+ return this.upToClass;
+ }
+
+ /**
+ * <p>
+ * Calls <code>java.lang.reflect.Field.get(Object)</code>.
+ * </p>
+ *
+ * @param field
+ * The Field to query.
+ * @return The Object from the given Field.
+ *
+ * @throws IllegalArgumentException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ * @throws IllegalAccessException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ *
+ * @see java.lang.reflect.Field#get(Object)
+ */
+ protected Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException {
+ return field.get(this.getObject());
+ }
+
+ /**
+ * <p>
+ * Gets whether or not to append static fields.
+ * </p>
+ *
+ * @return Whether or not to append static fields.
+ * @since 2.1
+ */
+ public boolean isAppendStatics() {
+ return this.appendStatics;
+ }
+
+ /**
+ * <p>
+ * Gets whether or not to append transient fields.
+ * </p>
+ *
+ * @return Whether or not to append transient fields.
+ */
+ public boolean isAppendTransients() {
+ return this.appendTransients;
+ }
+
+ /**
+ * <p>
+ * Append to the <code>toString</code> an <code>Object</code> array.
+ * </p>
+ *
+ * @param array
+ * the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ReflectionToStringBuilder reflectionAppendArray(Object array) {
+ this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
+ return this;
+ }
+
+ /**
+ * <p>
+ * Sets whether or not to append static fields.
+ * </p>
+ *
+ * @param appendStatics
+ * Whether or not to append static fields.
+ * @since 2.1
+ */
+ public void setAppendStatics(boolean appendStatics) {
+ this.appendStatics = appendStatics;
+ }
+
+ /**
+ * <p>
+ * Sets whether or not to append transient fields.
+ * </p>
+ *
+ * @param appendTransients
+ * Whether or not to append transient fields.
+ */
+ public void setAppendTransients(boolean appendTransients) {
+ this.appendTransients = appendTransients;
+ }
+
+ /**
+ * Sets the field names to exclude.
+ *
+ * @param excludeFieldNamesParam
+ * The excludeFieldNames to excluding from toString or <code>null</code>.
+ * @return <code>this</code>
+ */
+ public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam) {
+ if (excludeFieldNamesParam == null) {
+ this.excludeFieldNames = null;
+ } else {
+ //clone and remove nulls
+ this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
+ Arrays.sort(this.excludeFieldNames);
+ }
+ return this;
+ }
+
+ /**
+ * <p>
+ * Sets the last super class to stop appending fields for.
+ * </p>
+ *
+ * @param clazz
+ * The last super class to stop appending fields for.
+ */
+ public void setUpToClass(Class<?> clazz) {
+ if (clazz != null) {
+ Object object = getObject();
+ if (object != null && clazz.isInstance(object) == false) {
+ throw new IllegalArgumentException("Specified class is not a superclass of the object");
+ }
+ }
+ this.upToClass = clazz;
+ }
+
+ /**
+ * <p>
+ * Gets the String built by this builder.
+ * </p>
+ *
+ * @return the built string
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ return this.getStyle().getNullText();
+ }
+ Class<?> clazz = this.getObject().getClass();
+ this.appendFieldsIn(clazz);
+ while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
+ clazz = clazz.getSuperclass();
+ this.appendFieldsIn(clazz);
+ }
+ return super.toString();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/builder/StandardToStringStyle.java b/src/org/apache/commons/lang3/builder/StandardToStringStyle.java
new file mode 100644
index 0000000..b58b154
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/StandardToStringStyle.java
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+/**
+ * <p>Works with {@link ToStringBuilder} to create a <code>toString</code>.</p>
+ *
+ * <p>This class is intended to be used as a singleton.
+ * There is no need to instantiate a new style each time.
+ * Simply instantiate the class once, customize the values as required, and
+ * store the result in a public static final variable for the rest of the
+ * program to access.</p>
+ *
+ * @since 1.0
+ * @version $Id: StandardToStringStyle.java 1089740 2011-04-07 05:01:54Z bayard $
+ */
+public class StandardToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ */
+ public StandardToStringStyle() {
+ super();
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the class name.</p>
+ *
+ * @return the current useClassName flag
+ */
+ @Override
+ public boolean isUseClassName() { // NOPMD as this is implementing the abstract class
+ return super.isUseClassName();
+ }
+
+ /**
+ * <p>Sets whether to use the class name.</p>
+ *
+ * @param useClassName the new useClassName flag
+ */
+ @Override
+ public void setUseClassName(boolean useClassName) { // NOPMD as this is implementing the abstract class
+ super.setUseClassName(useClassName);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to output short or long class names.</p>
+ *
+ * @return the current useShortClassName flag
+ * @since 2.0
+ */
+ @Override
+ public boolean isUseShortClassName() { // NOPMD as this is implementing the abstract class
+ return super.isUseShortClassName();
+ }
+
+ /**
+ * <p>Sets whether to output short or long class names.</p>
+ *
+ * @param useShortClassName the new useShortClassName flag
+ * @since 2.0
+ */
+ @Override
+ public void setUseShortClassName(boolean useShortClassName) { // NOPMD as this is implementing the abstract class
+ super.setUseShortClassName(useShortClassName);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the identity hash code.</p>
+ * @return the current useIdentityHashCode flag
+ */
+ @Override
+ public boolean isUseIdentityHashCode() { // NOPMD as this is implementing the abstract class
+ return super.isUseIdentityHashCode();
+ }
+
+ /**
+ * <p>Sets whether to use the identity hash code.</p>
+ *
+ * @param useIdentityHashCode the new useIdentityHashCode flag
+ */
+ @Override
+ public void setUseIdentityHashCode(boolean useIdentityHashCode) { // NOPMD as this is implementing the abstract class
+ super.setUseIdentityHashCode(useIdentityHashCode);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the field names passed in.</p>
+ *
+ * @return the current useFieldNames flag
+ */
+ @Override
+ public boolean isUseFieldNames() { // NOPMD as this is implementing the abstract class
+ return super.isUseFieldNames();
+ }
+
+ /**
+ * <p>Sets whether to use the field names passed in.</p>
+ *
+ * @param useFieldNames the new useFieldNames flag
+ */
+ @Override
+ public void setUseFieldNames(boolean useFieldNames) { // NOPMD as this is implementing the abstract class
+ super.setUseFieldNames(useFieldNames);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use full detail when the caller doesn't
+ * specify.</p>
+ *
+ * @return the current defaultFullDetail flag
+ */
+ @Override
+ public boolean isDefaultFullDetail() { // NOPMD as this is implementing the abstract class
+ return super.isDefaultFullDetail();
+ }
+
+ /**
+ * <p>Sets whether to use full detail when the caller doesn't
+ * specify.</p>
+ *
+ * @param defaultFullDetail the new defaultFullDetail flag
+ */
+ @Override
+ public void setDefaultFullDetail(boolean defaultFullDetail) { // NOPMD as this is implementing the abstract class
+ super.setDefaultFullDetail(defaultFullDetail);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to output array content detail.</p>
+ *
+ * @return the current array content detail setting
+ */
+ @Override
+ public boolean isArrayContentDetail() { // NOPMD as this is implementing the abstract class
+ return super.isArrayContentDetail();
+ }
+
+ /**
+ * <p>Sets whether to output array content detail.</p>
+ *
+ * @param arrayContentDetail the new arrayContentDetail flag
+ */
+ @Override
+ public void setArrayContentDetail(boolean arrayContentDetail) { // NOPMD as this is implementing the abstract class
+ super.setArrayContentDetail(arrayContentDetail);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array start text.</p>
+ *
+ * @return the current array start text
+ */
+ @Override
+ public String getArrayStart() { // NOPMD as this is implementing the abstract class
+ return super.getArrayStart();
+ }
+
+ /**
+ * <p>Sets the array start text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arrayStart the new array start text
+ */
+ @Override
+ public void setArrayStart(String arrayStart) { // NOPMD as this is implementing the abstract class
+ super.setArrayStart(arrayStart);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array end text.</p>
+ *
+ * @return the current array end text
+ */
+ @Override
+ public String getArrayEnd() { // NOPMD as this is implementing the abstract class
+ return super.getArrayEnd();
+ }
+
+ /**
+ * <p>Sets the array end text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arrayEnd the new array end text
+ */
+ @Override
+ public void setArrayEnd(String arrayEnd) { // NOPMD as this is implementing the abstract class
+ super.setArrayEnd(arrayEnd);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array separator text.</p>
+ *
+ * @return the current array separator text
+ */
+ @Override
+ public String getArraySeparator() { // NOPMD as this is implementing the abstract class
+ return super.getArraySeparator();
+ }
+
+ /**
+ * <p>Sets the array separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arraySeparator the new array separator text
+ */
+ @Override
+ public void setArraySeparator(String arraySeparator) { // NOPMD as this is implementing the abstract class
+ super.setArraySeparator(arraySeparator);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the content start text.</p>
+ *
+ * @return the current content start text
+ */
+ @Override
+ public String getContentStart() { // NOPMD as this is implementing the abstract class
+ return super.getContentStart();
+ }
+
+ /**
+ * <p>Sets the content start text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param contentStart the new content start text
+ */
+ @Override
+ public void setContentStart(String contentStart) { // NOPMD as this is implementing the abstract class
+ super.setContentStart(contentStart);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the content end text.</p>
+ *
+ * @return the current content end text
+ */
+ @Override
+ public String getContentEnd() { // NOPMD as this is implementing the abstract class
+ return super.getContentEnd();
+ }
+
+ /**
+ * <p>Sets the content end text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param contentEnd the new content end text
+ */
+ @Override
+ public void setContentEnd(String contentEnd) { // NOPMD as this is implementing the abstract class
+ super.setContentEnd(contentEnd);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the field name value separator text.</p>
+ *
+ * @return the current field name value separator text
+ */
+ @Override
+ public String getFieldNameValueSeparator() { // NOPMD as this is implementing the abstract class
+ return super.getFieldNameValueSeparator();
+ }
+
+ /**
+ * <p>Sets the field name value separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param fieldNameValueSeparator the new field name value separator text
+ */
+ @Override
+ public void setFieldNameValueSeparator(String fieldNameValueSeparator) { // NOPMD as this is implementing the abstract class
+ super.setFieldNameValueSeparator(fieldNameValueSeparator);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the field separator text.</p>
+ *
+ * @return the current field separator text
+ */
+ @Override
+ public String getFieldSeparator() { // NOPMD as this is implementing the abstract class
+ return super.getFieldSeparator();
+ }
+
+ /**
+ * <p>Sets the field separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param fieldSeparator the new field separator text
+ */
+ @Override
+ public void setFieldSeparator(String fieldSeparator) { // NOPMD as this is implementing the abstract class
+ super.setFieldSeparator(fieldSeparator);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether the field separator should be added at the start
+ * of each buffer.</p>
+ *
+ * @return the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ @Override
+ public boolean isFieldSeparatorAtStart() { // NOPMD as this is implementing the abstract class
+ return super.isFieldSeparatorAtStart();
+ }
+
+ /**
+ * <p>Sets whether the field separator should be added at the start
+ * of each buffer.</p>
+ *
+ * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ @Override
+ public void setFieldSeparatorAtStart(boolean fieldSeparatorAtStart) { // NOPMD as this is implementing the abstract class
+ super.setFieldSeparatorAtStart(fieldSeparatorAtStart);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether the field separator should be added at the end
+ * of each buffer.</p>
+ *
+ * @return fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ @Override
+ public boolean isFieldSeparatorAtEnd() { // NOPMD as this is implementing the abstract class
+ return super.isFieldSeparatorAtEnd();
+ }
+
+ /**
+ * <p>Sets whether the field separator should be added at the end
+ * of each buffer.</p>
+ *
+ * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ @Override
+ public void setFieldSeparatorAtEnd(boolean fieldSeparatorAtEnd) { // NOPMD as this is implementing the abstract class
+ super.setFieldSeparatorAtEnd(fieldSeparatorAtEnd);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the text to output when <code>null</code> found.</p>
+ *
+ * @return the current text to output when <code>null</code> found
+ */
+ @Override
+ public String getNullText() { // NOPMD as this is implementing the abstract class
+ return super.getNullText();
+ }
+
+ /**
+ * <p>Sets the text to output when <code>null</code> found.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param nullText the new text to output when <code>null</code> found
+ */
+ @Override
+ public void setNullText(String nullText) { // NOPMD as this is implementing the abstract class
+ super.setNullText(nullText);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the text to output when a <code>Collection</code>,
+ * <code>Map</code> or <code>Array</code> size is output.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @return the current start of size text
+ */
+ @Override
+ public String getSizeStartText() { // NOPMD as this is implementing the abstract class
+ return super.getSizeStartText();
+ }
+
+ /**
+ * <p>Sets the start text to output when a <code>Collection</code>,
+ * <code>Map</code> or <code>Array</code> size is output.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param sizeStartText the new start of size text
+ */
+ @Override
+ public void setSizeStartText(String sizeStartText) { // NOPMD as this is implementing the abstract class
+ super.setSizeStartText(sizeStartText);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * Gets the end text to output when a <code>Collection</code>,
+ * <code>Map</code> or <code>Array</code> size is output.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of size text
+ */
+ @Override
+ public String getSizeEndText() { // NOPMD as this is implementing the abstract class
+ return super.getSizeEndText();
+ }
+
+ /**
+ * <p>Sets the end text to output when a <code>Collection</code>,
+ * <code>Map</code> or <code>Array</code> size is output.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param sizeEndText the new end of size text
+ */
+ @Override
+ public void setSizeEndText(String sizeEndText) { // NOPMD as this is implementing the abstract class
+ super.setSizeEndText(sizeEndText);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the start text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <P>This is output before the size value.</p>
+ *
+ * @return the current start of summary text
+ */
+ @Override
+ public String getSummaryObjectStartText() { // NOPMD as this is implementing the abstract class
+ return super.getSummaryObjectStartText();
+ }
+
+ /**
+ * <p>Sets the start text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectStartText the new start of summary text
+ */
+ @Override
+ public void setSummaryObjectStartText(String summaryObjectStartText) { // NOPMD as this is implementing the abstract class
+ super.setSummaryObjectStartText(summaryObjectStartText);
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the end text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of summary text
+ */
+ @Override
+ public String getSummaryObjectEndText() { // NOPMD as this is implementing the abstract class
+ return super.getSummaryObjectEndText();
+ }
+
+ /**
+ * <p>Sets the end text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectEndText the new end of summary text
+ */
+ @Override
+ public void setSummaryObjectEndText(String summaryObjectEndText) { // NOPMD as this is implementing the abstract class
+ super.setSummaryObjectEndText(summaryObjectEndText);
+ }
+
+ //---------------------------------------------------------------------
+
+}
diff --git a/src/org/apache/commons/lang3/builder/ToStringBuilder.java b/src/org/apache/commons/lang3/builder/ToStringBuilder.java
new file mode 100644
index 0000000..63c6268
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/ToStringBuilder.java
@@ -0,0 +1,1079 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * <p>Assists in implementing {@link Object#toString()} methods.</p>
+ *
+ * <p>This class enables a good and consistent <code>toString()</code> to be built for any
+ * class or object. This class aims to simplify the process by:</p>
+ * <ul>
+ * <li>allowing field names</li>
+ * <li>handling all types consistently</li>
+ * <li>handling nulls consistently</li>
+ * <li>outputting arrays and multi-dimensional arrays</li>
+ * <li>enabling the detail level to be controlled for Objects and Collections</li>
+ * <li>handling class hierarchies</li>
+ * </ul>
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ *
+ * ...
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * append("name", name).
+ * append("age", age).
+ * append("smoker", smoker).
+ * toString();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>This will produce a toString of the format:
+ * <code>Person@7f54[name=Stephen,age=29,smoker=false]</code></p>
+ *
+ * <p>To add the superclass <code>toString</code>, use {@link #appendSuper}.
+ * To append the <code>toString</code> from an object that is delegated
+ * to (or any other object), use {@link #appendToString}.</p>
+ *
+ * <p>Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * <code>reflectionToString</code>, uses <code>AccessibleObject.setAccessible</code> to
+ * change the visibility of the fields. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than testing explicitly.</p>
+ *
+ * <p>A typical invocation for this method would look like:</p>
+ *
+ * <pre>
+ * public String toString() {
+ * return ToStringBuilder.reflectionToString(this);
+ * }
+ * </pre>
+ *
+ * <p>You can also use the builder to debug 3rd party objects:</p>
+ *
+ * <pre>
+ * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
+ * </pre>
+ *
+ * <p>The exact format of the <code>toString</code> is determined by
+ * the {@link ToStringStyle} passed into the constructor.</p>
+ *
+ * @since 1.0
+ * @version $Id: ToStringBuilder.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public class ToStringBuilder implements Builder<String> {
+
+ /**
+ * The default style of output to use, not null.
+ */
+ private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE;
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Gets the default <code>ToStringStyle</code> to use.</p>
+ *
+ * <p>This method gets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a <code>ToStringStyle</code> to the constructor instead
+ * of using this global default.</p>
+ *
+ * <p>This method can be used from multiple threads.
+ * Internally, a <code>volatile</code> variable is used to provide the guarantee
+ * that the latest value set using {@link #setDefaultStyle} is the value returned.
+ * It is strongly recommended that the default style is only changed during application startup.</p>
+ *
+ * <p>One reason for changing the default could be to have a verbose style during
+ * development and a compact style in production.</p>
+ *
+ * @return the default <code>ToStringStyle</code>, never null
+ */
+ public static ToStringStyle getDefaultStyle() {
+ return defaultStyle;
+ }
+
+ /**
+ * <p>Sets the default <code>ToStringStyle</code> to use.</p>
+ *
+ * <p>This method sets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a <code>ToStringStyle</code> to the constructor instead
+ * of changing this global default.</p>
+ *
+ * <p>This method is not intended for use from multiple threads.
+ * Internally, a <code>volatile</code> variable is used to provide the guarantee
+ * that the latest value set is the value returned from {@link #getDefaultStyle}.</p>
+ *
+ * @param style the default <code>ToStringStyle</code>
+ * @throws IllegalArgumentException if the style is <code>null</code>
+ */
+ public static void setDefaultStyle(ToStringStyle style) {
+ if (style == null) {
+ throw new IllegalArgumentException("The style must not be null");
+ }
+ defaultStyle = style;
+ }
+
+ //----------------------------------------------------------------------------
+ /**
+ * <p>Uses <code>ReflectionToStringBuilder</code> to generate a
+ * <code>toString</code> for the specified object.</p>
+ *
+ * @param object the Object to be output
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object)
+ */
+ public static String reflectionToString(Object object) {
+ return ReflectionToStringBuilder.toString(object);
+ }
+
+ /**
+ * <p>Uses <code>ReflectionToStringBuilder</code> to generate a
+ * <code>toString</code> for the specified object.</p>
+ *
+ * @param object the Object to be output
+ * @param style the style of the <code>toString</code> to create, may be <code>null</code>
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle)
+ */
+ public static String reflectionToString(Object object, ToStringStyle style) {
+ return ReflectionToStringBuilder.toString(object, style);
+ }
+
+ /**
+ * <p>Uses <code>ReflectionToStringBuilder</code> to generate a
+ * <code>toString</code> for the specified object.</p>
+ *
+ * @param object the Object to be output
+ * @param style the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param outputTransients whether to include transient fields
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean)
+ */
+ public static String reflectionToString(Object object, ToStringStyle style, boolean outputTransients) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ * <p>Uses <code>ReflectionToStringBuilder</code> to generate a
+ * <code>toString</code> for the specified object.</p>
+ *
+ * @param <T> the type of the object
+ * @param object the Object to be output
+ * @param style the style of the <code>toString</code> to create, may be <code>null</code>
+ * @param outputTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive), may be <code>null</code>
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,boolean,Class)
+ * @since 2.0
+ */
+ public static <T> String reflectionToString(
+ T object,
+ ToStringStyle style,
+ boolean outputTransients,
+ Class<? super T> reflectUpToClass) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * Current toString buffer, not null.
+ */
+ private final StringBuffer buffer;
+ /**
+ * The object being output, may be null.
+ */
+ private final Object object;
+ /**
+ * The style of output to use, not null.
+ */
+ private final ToStringStyle style;
+
+ /**
+ * <p>Constructs a builder for the specified object using the default output style.</p>
+ *
+ * <p>This default style is obtained from {@link #getDefaultStyle()}.</p>
+ *
+ * @param object the Object to build a <code>toString</code> for, not recommended to be null
+ */
+ public ToStringBuilder(Object object) {
+ this(object, null, null);
+ }
+
+ /**
+ * <p>Constructs a builder for the specified object using the a defined output style.</p>
+ *
+ * <p>If the style is <code>null</code>, the default style is used.</p>
+ *
+ * @param object the Object to build a <code>toString</code> for, not recommended to be null
+ * @param style the style of the <code>toString</code> to create, null uses the default style
+ */
+ public ToStringBuilder(Object object, ToStringStyle style) {
+ this(object, style, null);
+ }
+
+ /**
+ * <p>Constructs a builder for the specified object.</p>
+ *
+ * <p>If the style is <code>null</code>, the default style is used.</p>
+ *
+ * <p>If the buffer is <code>null</code>, a new one is created.</p>
+ *
+ * @param object the Object to build a <code>toString</code> for, not recommended to be null
+ * @param style the style of the <code>toString</code> to create, null uses the default style
+ * @param buffer the <code>StringBuffer</code> to populate, may be null
+ */
+ public ToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
+ if (style == null) {
+ style = getDefaultStyle();
+ }
+ if (buffer == null) {
+ buffer = new StringBuffer(512);
+ }
+ this.buffer = buffer;
+ this.style = style;
+ this.object = object;
+
+ style.appendStart(buffer, object);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(boolean value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(boolean[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(byte value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(byte[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(char value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(char[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(double value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(double[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(float value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(float[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(int value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(int[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(long value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(long[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value.</p>
+ *
+ * @param obj the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(Object obj) {
+ style.append(buffer, null, obj, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(Object[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * value.</p>
+ *
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(short value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(short[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>hashCode</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, boolean[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>byte</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code> array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, byte[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, char[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, double[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>float</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, float[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, int[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, long[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object obj) {
+ style.append(buffer, fieldName, obj, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail,
+ * <code>false</code> for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object obj, boolean fullDetail) {
+ style.append(buffer, fieldName, obj, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, Object[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>short</code>
+ * value.</p>
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * array.</p>
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting <code>true</code> will output the array in full. Setting
+ * <code>false</code> will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(String fieldName, short[] array, boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * <p>Appends with the same format as the default <code>Object toString()
+ * </code> method. Appends the class name followed by
+ * {@link System#identityHashCode(java.lang.Object)}.</p>
+ *
+ * @param object the <code>Object</code> whose class name and id to output
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendAsObjectToString(Object object) {
+ ObjectUtils.identityToString(this.getStringBuffer(), object);
+ return this;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append the <code>toString</code> from the superclass.</p>
+ *
+ * <p>This method assumes that the superclass uses the same <code>ToStringStyle</code>
+ * as this one.</p>
+ *
+ * <p>If <code>superToString</code> is <code>null</code>, no change is made.</p>
+ *
+ * @param superToString the result of <code>super.toString()</code>
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendSuper(String superToString) {
+ if (superToString != null) {
+ style.appendSuper(buffer, superToString);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Append the <code>toString</code> from another object.</p>
+ *
+ * <p>This method is useful where a class delegates most of the implementation of
+ * its properties to another class. You can then call <code>toString()</code> on
+ * the other class and pass the result into this method.</p>
+ *
+ * <pre>
+ * private AnotherObject delegate;
+ * private String fieldInThisClass;
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * appendToString(delegate.toString()).
+ * append(fieldInThisClass).
+ * toString();
+ * }</pre>
+ *
+ * <p>This method assumes that the other object uses the same <code>ToStringStyle</code>
+ * as this one.</p>
+ *
+ * <p>If the <code>toString</code> is <code>null</code>, no change is made.</p>
+ *
+ * @param toString the result of <code>toString()</code> on another object
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendToString(String toString) {
+ if (toString != null) {
+ style.appendToString(buffer, toString);
+ }
+ return this;
+ }
+
+ /**
+ * <p>Returns the <code>Object</code> being output.</p>
+ *
+ * @return The object being output.
+ * @since 2.0
+ */
+ public Object getObject() {
+ return object;
+ }
+
+ /**
+ * <p>Gets the <code>StringBuffer</code> being populated.</p>
+ *
+ * @return the <code>StringBuffer</code> being populated
+ */
+ public StringBuffer getStringBuffer() {
+ return buffer;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Gets the <code>ToStringStyle</code> being used.</p>
+ *
+ * @return the <code>ToStringStyle</code> being used
+ * @since 2.0
+ */
+ public ToStringStyle getStyle() {
+ return style;
+ }
+
+ /**
+ * <p>Returns the built <code>toString</code>.</p>
+ *
+ * <p>This method appends the end of data indicator, and can only be called once.
+ * Use {@link #getStringBuffer} to get the current string state.</p>
+ *
+ * <p>If the object is <code>null</code>, return the style's <code>nullText</code></p>
+ *
+ * @return the String <code>toString</code>
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ this.getStringBuffer().append(this.getStyle().getNullText());
+ } else {
+ style.appendEnd(this.getStringBuffer(), this.getObject());
+ }
+ return this.getStringBuffer().toString();
+ }
+
+ /**
+ * Returns the String that was build as an object representation. The
+ * default implementation utilizes the {@link #toString()} implementation.
+ *
+ * @return the String <code>toString</code>
+ *
+ * @see #toString()
+ *
+ * @since 3.0
+ */
+ public String build() {
+ return toString();
+ }
+}
diff --git a/src/org/apache/commons/lang3/builder/ToStringStyle.java b/src/org/apache/commons/lang3/builder/ToStringStyle.java
new file mode 100644
index 0000000..d9ee587
--- /dev/null
+++ b/src/org/apache/commons/lang3/builder/ToStringStyle.java
@@ -0,0 +1,2271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.builder;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * <p>Controls <code>String</code> formatting for {@link ToStringBuilder}.
+ * The main public interface is always via <code>ToStringBuilder</code>.</p>
+ *
+ * <p>These classes are intended to be used as <code>Singletons</code>.
+ * There is no need to instantiate a new style each time. A program
+ * will generally use one of the predefined constants on this class.
+ * Alternatively, the {@link StandardToStringStyle} class can be used
+ * to set the individual settings. Thus most styles can be achieved
+ * without subclassing.</p>
+ *
+ * <p>If required, a subclass can override as many or as few of the
+ * methods as it requires. Each object type (from <code>boolean</code>
+ * to <code>long</code> to <code>Object</code> to <code>int[]</code>) has
+ * its own methods to output it. Most have two versions, detail and summary.
+ *
+ * <p>For example, the detail version of the array based methods will
+ * output the whole array, whereas the summary method will just output
+ * the array length.</p>
+ *
+ * <p>If you want to format the output of certain objects, such as dates, you
+ * must create a subclass and override a method.
+ * <pre>
+ * public class MyStyle extends ToStringStyle {
+ * protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ * if (value instanceof Date) {
+ * value = new SimpleDateFormat("yyyy-MM-dd").format(value);
+ * }
+ * buffer.append(value);
+ * }
+ * }
+ * </pre>
+ * </p>
+ *
+ * @since 1.0
+ * @version $Id: ToStringStyle.java 1091066 2011-04-11 13:30:11Z mbenson $
+ */
+public abstract class ToStringStyle implements Serializable {
+
+ /**
+ * Serialization version ID.
+ */
+ private static final long serialVersionUID = -2587890625525655916L;
+
+ /**
+ * The default toString style. Using the Using the <code>Person</code>
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person@182f0db[name=John Doe,age=33,smoker=false]
+ * </pre>
+ */
+ public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
+
+ /**
+ * The multi line toString style. Using the Using the <code>Person</code>
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person@182f0db[
+ * name=John Doe
+ * age=33
+ * smoker=false
+ * ]
+ * </pre>
+ */
+ public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();
+
+ /**
+ * The no field names toString style. Using the Using the
+ * <code>Person</code> example from {@link ToStringBuilder}, the output
+ * would look like this:
+ *
+ * <pre>
+ * Person@182f0db[John Doe,33,false]
+ * </pre>
+ */
+ public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle();
+
+ /**
+ * The short prefix toString style. Using the <code>Person</code> example
+ * from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person[name=John Doe,age=33,smoker=false]
+ * </pre>
+ *
+ * @since 2.1
+ */
+ public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle();
+
+ /**
+ * The simple toString style. Using the Using the <code>Person</code>
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * John Doe,33,false
+ * </pre>
+ */
+ public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();
+
+ /**
+ * <p>
+ * A registry of objects used by <code>reflectionToString</code> methods
+ * to detect cyclical object references and avoid infinite loops.
+ * </p>
+ */
+ private static final ThreadLocal<WeakHashMap<Object, Object>> REGISTRY =
+ new ThreadLocal<WeakHashMap<Object,Object>>();
+
+ /**
+ * <p>
+ * Returns the registry of objects being traversed by the <code>reflectionToString</code>
+ * methods in the current thread.
+ * </p>
+ *
+ * @return Set the registry of objects being traversed
+ */
+ static Map<Object, Object> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * <p>
+ * Returns <code>true</code> if the registry contains the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean <code>true</code> if the registry contains the given
+ * object.
+ */
+ static boolean isRegistered(Object value) {
+ Map<Object, Object> m = getRegistry();
+ return m != null && m.containsKey(value);
+ }
+
+ /**
+ * <p>
+ * Registers the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to register.
+ */
+ static void register(Object value) {
+ if (value != null) {
+ Map<Object, Object> m = getRegistry();
+ if (m == null) {
+ REGISTRY.set(new WeakHashMap<Object, Object>());
+ }
+ getRegistry().put(value, null);
+ }
+ }
+
+ /**
+ * <p>
+ * Unregisters the given object.
+ * </p>
+ *
+ * <p>
+ * Used by the reflection methods to avoid infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to unregister.
+ */
+ static void unregister(Object value) {
+ if (value != null) {
+ Map<Object, Object> m = getRegistry();
+ if (m != null) {
+ m.remove(value);
+ if (m.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether to use the field names, the default is <code>true</code>.
+ */
+ private boolean useFieldNames = true;
+
+ /**
+ * Whether to use the class name, the default is <code>true</code>.
+ */
+ private boolean useClassName = true;
+
+ /**
+ * Whether to use short class names, the default is <code>false</code>.
+ */
+ private boolean useShortClassName = false;
+
+ /**
+ * Whether to use the identity hash code, the default is <code>true</code>.
+ */
+ private boolean useIdentityHashCode = true;
+
+ /**
+ * The content start <code>'['</code>.
+ */
+ private String contentStart = "[";
+
+ /**
+ * The content end <code>']'</code>.
+ */
+ private String contentEnd = "]";
+
+ /**
+ * The field name value separator <code>'='</code>.
+ */
+ private String fieldNameValueSeparator = "=";
+
+ /**
+ * Whether the field separator should be added before any other fields.
+ */
+ private boolean fieldSeparatorAtStart = false;
+
+ /**
+ * Whether the field separator should be added after any other fields.
+ */
+ private boolean fieldSeparatorAtEnd = false;
+
+ /**
+ * The field separator <code>','</code>.
+ */
+ private String fieldSeparator = ",";
+
+ /**
+ * The array start <code>'{'</code>.
+ */
+ private String arrayStart = "{";
+
+ /**
+ * The array separator <code>','</code>.
+ */
+ private String arraySeparator = ",";
+
+ /**
+ * The detail for array content.
+ */
+ private boolean arrayContentDetail = true;
+
+ /**
+ * The array end <code>'}'</code>.
+ */
+ private String arrayEnd = "}";
+
+ /**
+ * The value to use when fullDetail is <code>null</code>,
+ * the default value is <code>true</code>.
+ */
+ private boolean defaultFullDetail = true;
+
+ /**
+ * The <code>null</code> text <code>'&lt;null&gt;'</code>.
+ */
+ private String nullText = "<null>";
+
+ /**
+ * The summary size text start <code>'<size'</code>.
+ */
+ private String sizeStartText = "<size=";
+
+ /**
+ * The summary size text start <code>'&gt;'</code>.
+ */
+ private String sizeEndText = ">";
+
+ /**
+ * The summary object text start <code>'&lt;'</code>.
+ */
+ private String summaryObjectStartText = "<";
+
+ /**
+ * The summary object text start <code>'&gt;'</code>.
+ */
+ private String summaryObjectEndText = ">";
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Constructor.</p>
+ */
+ protected ToStringStyle() {
+ super();
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> the superclass toString.</p>
+ * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle. </p>
+ *
+ * <p>A <code>null</code> <code>superToString</code> is ignored.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param superToString the <code>super.toString()</code>
+ * @since 2.0
+ */
+ public void appendSuper(StringBuffer buffer, String superToString) {
+ appendToString(buffer, superToString);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> another toString.</p>
+ * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle. </p>
+ *
+ * <p>A <code>null</code> <code>toString</code> is ignored.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param toString the additional <code>toString</code>
+ * @since 2.0
+ */
+ public void appendToString(StringBuffer buffer, String toString) {
+ if (toString != null) {
+ int pos1 = toString.indexOf(contentStart) + contentStart.length();
+ int pos2 = toString.lastIndexOf(contentEnd);
+ if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) {
+ String data = toString.substring(pos1, pos2);
+ if (fieldSeparatorAtStart) {
+ removeLastFieldSeparator(buffer);
+ }
+ buffer.append(data);
+ appendFieldSeparator(buffer);
+ }
+ }
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the start of data indicator.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param object the <code>Object</code> to build a <code>toString</code> for
+ */
+ public void appendStart(StringBuffer buffer, Object object) {
+ if (object != null) {
+ appendClassName(buffer, object);
+ appendIdentityHashCode(buffer, object);
+ appendContentStart(buffer);
+ if (fieldSeparatorAtStart) {
+ appendFieldSeparator(buffer);
+ }
+ }
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the end of data indicator.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param object the <code>Object</code> to build a
+ * <code>toString</code> for.
+ */
+ public void appendEnd(StringBuffer buffer, Object object) {
+ if (this.fieldSeparatorAtEnd == false) {
+ removeLastFieldSeparator(buffer);
+ }
+ appendContentEnd(buffer);
+ unregister(object);
+ }
+
+ /**
+ * <p>Remove the last field separator from the buffer.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @since 2.0
+ */
+ protected void removeLastFieldSeparator(StringBuffer buffer) {
+ int len = buffer.length();
+ int sepLen = fieldSeparator.length();
+ if (len > 0 && sepLen > 0 && len >= sepLen) {
+ boolean match = true;
+ for (int i = 0; i < sepLen; i++) {
+ if (buffer.charAt(len - 1 - i) != fieldSeparator.charAt(sepLen - 1 - i)) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ buffer.setLength(len - sepLen);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value, printing the full <code>toString</code> of the
+ * <code>Object</code> passed in.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (value == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, value, isFullDetail(fullDetail));
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>,
+ * correctly interpreting its type.</p>
+ *
+ * <p>This method performs the main lookup by Class type to correctly
+ * route arrays, <code>Collections</code>, <code>Maps</code> and
+ * <code>Objects</code> to the appropriate method.</p>
+ *
+ * <p>Either detail or summary views can be specified.</p>
+ *
+ * <p>If a cycle is detected, an object will be appended with the
+ * <code>Object.toString()</code> format.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>,
+ * not <code>null</code>
+ * @param detail output detail or not
+ */
+ protected void appendInternal(StringBuffer buffer, String fieldName, Object value, boolean detail) {
+ if (isRegistered(value)
+ && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
+ appendCyclicObject(buffer, fieldName, value);
+ return;
+ }
+
+ register(value);
+
+ try {
+ if (value instanceof Collection<?>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Collection<?>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Collection<?>) value).size());
+ }
+
+ } else if (value instanceof Map<?, ?>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Map<?, ?>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Map<?, ?>) value).size());
+ }
+
+ } else if (value instanceof long[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (long[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (long[]) value);
+ }
+
+ } else if (value instanceof int[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (int[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (int[]) value);
+ }
+
+ } else if (value instanceof short[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (short[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (short[]) value);
+ }
+
+ } else if (value instanceof byte[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (byte[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (byte[]) value);
+ }
+
+ } else if (value instanceof char[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (char[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (char[]) value);
+ }
+
+ } else if (value instanceof double[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (double[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (double[]) value);
+ }
+
+ } else if (value instanceof float[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (float[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (float[]) value);
+ }
+
+ } else if (value instanceof boolean[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (boolean[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (boolean[]) value);
+ }
+
+ } else if (value.getClass().isArray()) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Object[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (Object[]) value);
+ }
+
+ } else {
+ if (detail) {
+ appendDetail(buffer, fieldName, value);
+ } else {
+ appendSummary(buffer, fieldName, value);
+ }
+ }
+ } finally {
+ unregister(value);
+ }
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value that has been detected to participate in a cycle. This
+ * implementation will print the standard string value of the value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>,
+ * not <code>null</code>
+ *
+ * @since 2.2
+ */
+ protected void appendCyclicObject(StringBuffer buffer, String fieldName, Object value) {
+ ObjectUtils.identityToString(buffer, value);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value, printing the full detail of the <code>Object</code>.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ buffer.append(value);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>Collection</code>.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param coll the <code>Collection</code> to add to the
+ * <code>toString</code>, not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
+ buffer.append(coll);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>Map<code>.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param map the <code>Map</code> to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
+ buffer.append(map);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * value, printing a summary of the <code>Object</code>.</P>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, Object value) {
+ buffer.append(summaryObjectStartText);
+ buffer.append(getShortClassName(value.getClass()));
+ buffer.append(summaryObjectEndText);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, long value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, long value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, int value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, int value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, short value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, short value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, byte value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, byte value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, char value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, char value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, double value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, double value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, float value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, float value) {
+ buffer.append(value);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param value the value to add to the <code>toString</code>
+ */
+ public void append(StringBuffer buffer, String fieldName, boolean value) {
+ appendFieldStart(buffer, fieldName);
+ appendDetail(buffer, fieldName, value);
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * value.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param value the value to add to the <code>toString</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, boolean value) {
+ buffer.append(value);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, Object[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of an
+ * <code>Object</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, Object[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ Object item = array[i];
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ if (item == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, item, arrayContentDetail);
+ }
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of an array type.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ * @since 2.0
+ */
+ protected void reflectionAppendArrayDetail(StringBuffer buffer, String fieldName, Object array) {
+ buffer.append(arrayStart);
+ int length = Array.getLength(array);
+ for (int i = 0; i < length; i++) {
+ Object item = Array.get(array, i);
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ if (item == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, item, arrayContentDetail);
+ }
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of an
+ * <code>Object</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, Object[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>long</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, long[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>long</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, long[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>long</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, long[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>int</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, int[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of an
+ * <code>int</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, int[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of an
+ * <code>int</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, int[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>short</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, short[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>short</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, short[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>short</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, short[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>byte</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, byte[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>byte</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, byte[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>byte</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, byte[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>char</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the <code>toString</code>
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, char[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>char</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, char[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>char</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, char[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>double</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, double[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>double</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, double[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>double</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, double[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>float</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, float[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>float</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, float[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>float</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, float[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> a <code>boolean</code>
+ * array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ * @param array the array to add to the toString
+ * @param fullDetail <code>true</code> for detail, <code>false</code>
+ * for summary info, <code>null</code> for style decides
+ */
+ public void append(StringBuffer buffer, String fieldName, boolean[] array, Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (array == null) {
+ appendNullText(buffer, fieldName);
+
+ } else if (isFullDetail(fullDetail)) {
+ appendDetail(buffer, fieldName, array);
+
+ } else {
+ appendSummary(buffer, fieldName, array);
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the detail of a
+ * <code>boolean</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendDetail(StringBuffer buffer, String fieldName, boolean[] array) {
+ buffer.append(arrayStart);
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ appendDetail(buffer, fieldName, array[i]);
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a summary of a
+ * <code>boolean</code> array.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void appendSummary(StringBuffer buffer, String fieldName, boolean[] array) {
+ appendSummarySize(buffer, fieldName, array.length);
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Append to the <code>toString</code> the class name.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param object the <code>Object</code> whose name to output
+ */
+ protected void appendClassName(StringBuffer buffer, Object object) {
+ if (useClassName && object != null) {
+ register(object);
+ if (useShortClassName) {
+ buffer.append(getShortClassName(object.getClass()));
+ } else {
+ buffer.append(object.getClass().getName());
+ }
+ }
+ }
+
+ /**
+ * <p>Append the {@link System#identityHashCode(java.lang.Object)}.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param object the <code>Object</code> whose id to output
+ */
+ protected void appendIdentityHashCode(StringBuffer buffer, Object object) {
+ if (this.isUseIdentityHashCode() && object!=null) {
+ register(object);
+ buffer.append('@');
+ buffer.append(Integer.toHexString(System.identityHashCode(object)));
+ }
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the content start.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ */
+ protected void appendContentStart(StringBuffer buffer) {
+ buffer.append(contentStart);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the content end.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ */
+ protected void appendContentEnd(StringBuffer buffer) {
+ buffer.append(contentEnd);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an indicator for <code>null</code>.</p>
+ *
+ * <p>The default indicator is <code>'&lt;null&gt;'</code>.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ */
+ protected void appendNullText(StringBuffer buffer, String fieldName) {
+ buffer.append(nullText);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the field separator.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ */
+ protected void appendFieldSeparator(StringBuffer buffer) {
+ buffer.append(fieldSeparator);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> the field start.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name
+ */
+ protected void appendFieldStart(StringBuffer buffer, String fieldName) {
+ if (useFieldNames && fieldName != null) {
+ buffer.append(fieldName);
+ buffer.append(fieldNameValueSeparator);
+ }
+ }
+
+ /**
+ * <p>Append to the <code>toString<code> the field end.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ */
+ protected void appendFieldEnd(StringBuffer buffer, String fieldName) {
+ appendFieldSeparator(buffer);
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> a size summary.</p>
+ *
+ * <p>The size summary is used to summarize the contents of
+ * <code>Collections</code>, <code>Maps</code> and arrays.</p>
+ *
+ * <p>The output consists of a prefix, the passed in size
+ * and a suffix.</p>
+ *
+ * <p>The default format is <code>'&lt;size=n&gt;'<code>.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param size the size to append
+ */
+ protected void appendSummarySize(StringBuffer buffer, String fieldName, int size) {
+ buffer.append(sizeStartText);
+ buffer.append(size);
+ buffer.append(sizeEndText);
+ }
+
+ /**
+ * <p>Is this field to be output in full detail.</p>
+ *
+ * <p>This method converts a detail request into a detail level.
+ * The calling code may request full detail (<code>true</code>),
+ * but a subclass might ignore that and always return
+ * <code>false</code>. The calling code may pass in
+ * <code>null</code> indicating that it doesn't care about
+ * the detail level. In this case the default detail level is
+ * used.</p>
+ *
+ * @param fullDetailRequest the detail level requested
+ * @return whether full detail is to be shown
+ */
+ protected boolean isFullDetail(Boolean fullDetailRequest) {
+ if (fullDetailRequest == null) {
+ return defaultFullDetail;
+ }
+ return fullDetailRequest.booleanValue();
+ }
+
+ /**
+ * <p>Gets the short class name for a class.</p>
+ *
+ * <p>The short class name is the classname excluding
+ * the package name.</p>
+ *
+ * @param cls the <code>Class</code> to get the short name of
+ * @return the short name
+ */
+ protected String getShortClassName(Class<?> cls) {
+ return ClassUtils.getShortClassName(cls);
+ }
+
+ // Setters and getters for the customizable parts of the style
+ // These methods are not expected to be overridden, except to make public
+ // (They are not public so that immutable subclasses can be written)
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the class name.</p>
+ *
+ * @return the current useClassName flag
+ */
+ protected boolean isUseClassName() {
+ return useClassName;
+ }
+
+ /**
+ * <p>Sets whether to use the class name.</p>
+ *
+ * @param useClassName the new useClassName flag
+ */
+ protected void setUseClassName(boolean useClassName) {
+ this.useClassName = useClassName;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to output short or long class names.</p>
+ *
+ * @return the current useShortClassName flag
+ * @since 2.0
+ */
+ protected boolean isUseShortClassName() {
+ return useShortClassName;
+ }
+
+ /**
+ * <p>Sets whether to output short or long class names.</p>
+ *
+ * @param useShortClassName the new useShortClassName flag
+ * @since 2.0
+ */
+ protected void setUseShortClassName(boolean useShortClassName) {
+ this.useShortClassName = useShortClassName;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the identity hash code.</p>
+ *
+ * @return the current useIdentityHashCode flag
+ */
+ protected boolean isUseIdentityHashCode() {
+ return useIdentityHashCode;
+ }
+
+ /**
+ * <p>Sets whether to use the identity hash code.</p>
+ *
+ * @param useIdentityHashCode the new useIdentityHashCode flag
+ */
+ protected void setUseIdentityHashCode(boolean useIdentityHashCode) {
+ this.useIdentityHashCode = useIdentityHashCode;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use the field names passed in.</p>
+ *
+ * @return the current useFieldNames flag
+ */
+ protected boolean isUseFieldNames() {
+ return useFieldNames;
+ }
+
+ /**
+ * <p>Sets whether to use the field names passed in.</p>
+ *
+ * @param useFieldNames the new useFieldNames flag
+ */
+ protected void setUseFieldNames(boolean useFieldNames) {
+ this.useFieldNames = useFieldNames;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to use full detail when the caller doesn't
+ * specify.</p>
+ *
+ * @return the current defaultFullDetail flag
+ */
+ protected boolean isDefaultFullDetail() {
+ return defaultFullDetail;
+ }
+
+ /**
+ * <p>Sets whether to use full detail when the caller doesn't
+ * specify.</p>
+ *
+ * @param defaultFullDetail the new defaultFullDetail flag
+ */
+ protected void setDefaultFullDetail(boolean defaultFullDetail) {
+ this.defaultFullDetail = defaultFullDetail;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether to output array content detail.</p>
+ *
+ * @return the current array content detail setting
+ */
+ protected boolean isArrayContentDetail() {
+ return arrayContentDetail;
+ }
+
+ /**
+ * <p>Sets whether to output array content detail.</p>
+ *
+ * @param arrayContentDetail the new arrayContentDetail flag
+ */
+ protected void setArrayContentDetail(boolean arrayContentDetail) {
+ this.arrayContentDetail = arrayContentDetail;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array start text.</p>
+ *
+ * @return the current array start text
+ */
+ protected String getArrayStart() {
+ return arrayStart;
+ }
+
+ /**
+ * <p>Sets the array start text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arrayStart the new array start text
+ */
+ protected void setArrayStart(String arrayStart) {
+ if (arrayStart == null) {
+ arrayStart = "";
+ }
+ this.arrayStart = arrayStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array end text.</p>
+ *
+ * @return the current array end text
+ */
+ protected String getArrayEnd() {
+ return arrayEnd;
+ }
+
+ /**
+ * <p>Sets the array end text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arrayEnd the new array end text
+ */
+ protected void setArrayEnd(String arrayEnd) {
+ if (arrayEnd == null) {
+ arrayEnd = "";
+ }
+ this.arrayEnd = arrayEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the array separator text.</p>
+ *
+ * @return the current array separator text
+ */
+ protected String getArraySeparator() {
+ return arraySeparator;
+ }
+
+ /**
+ * <p>Sets the array separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arraySeparator the new array separator text
+ */
+ protected void setArraySeparator(String arraySeparator) {
+ if (arraySeparator == null) {
+ arraySeparator = "";
+ }
+ this.arraySeparator = arraySeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the content start text.</p>
+ *
+ * @return the current content start text
+ */
+ protected String getContentStart() {
+ return contentStart;
+ }
+
+ /**
+ * <p>Sets the content start text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param contentStart the new content start text
+ */
+ protected void setContentStart(String contentStart) {
+ if (contentStart == null) {
+ contentStart = "";
+ }
+ this.contentStart = contentStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the content end text.</p>
+ *
+ * @return the current content end text
+ */
+ protected String getContentEnd() {
+ return contentEnd;
+ }
+
+ /**
+ * <p>Sets the content end text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param contentEnd the new content end text
+ */
+ protected void setContentEnd(String contentEnd) {
+ if (contentEnd == null) {
+ contentEnd = "";
+ }
+ this.contentEnd = contentEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the field name value separator text.</p>
+ *
+ * @return the current field name value separator text
+ */
+ protected String getFieldNameValueSeparator() {
+ return fieldNameValueSeparator;
+ }
+
+ /**
+ * <p>Sets the field name value separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param fieldNameValueSeparator the new field name value separator text
+ */
+ protected void setFieldNameValueSeparator(String fieldNameValueSeparator) {
+ if (fieldNameValueSeparator == null) {
+ fieldNameValueSeparator = "";
+ }
+ this.fieldNameValueSeparator = fieldNameValueSeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the field separator text.</p>
+ *
+ * @return the current field separator text
+ */
+ protected String getFieldSeparator() {
+ return fieldSeparator;
+ }
+
+ /**
+ * <p>Sets the field separator text.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param fieldSeparator the new field separator text
+ */
+ protected void setFieldSeparator(String fieldSeparator) {
+ if (fieldSeparator == null) {
+ fieldSeparator = "";
+ }
+ this.fieldSeparator = fieldSeparator;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether the field separator should be added at the start
+ * of each buffer.</p>
+ *
+ * @return the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ protected boolean isFieldSeparatorAtStart() {
+ return fieldSeparatorAtStart;
+ }
+
+ /**
+ * <p>Sets whether the field separator should be added at the start
+ * of each buffer.</p>
+ *
+ * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag
+ * @since 2.0
+ */
+ protected void setFieldSeparatorAtStart(boolean fieldSeparatorAtStart) {
+ this.fieldSeparatorAtStart = fieldSeparatorAtStart;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets whether the field separator should be added at the end
+ * of each buffer.</p>
+ *
+ * @return fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ protected boolean isFieldSeparatorAtEnd() {
+ return fieldSeparatorAtEnd;
+ }
+
+ /**
+ * <p>Sets whether the field separator should be added at the end
+ * of each buffer.</p>
+ *
+ * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ protected void setFieldSeparatorAtEnd(boolean fieldSeparatorAtEnd) {
+ this.fieldSeparatorAtEnd = fieldSeparatorAtEnd;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the text to output when <code>null</code> found.</p>
+ *
+ * @return the current text to output when null found
+ */
+ protected String getNullText() {
+ return nullText;
+ }
+
+ /**
+ * <p>Sets the text to output when <code>null</code> found.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param nullText the new text to output when null found
+ */
+ protected void setNullText(String nullText) {
+ if (nullText == null) {
+ nullText = "";
+ }
+ this.nullText = nullText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the start text to output when a <code>Collection</code>,
+ * <code>Map</code> or array size is output.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @return the current start of size text
+ */
+ protected String getSizeStartText() {
+ return sizeStartText;
+ }
+
+ /**
+ * <p>Sets the start text to output when a <code>Collection</code>,
+ * <code>Map</code> or array size is output.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param sizeStartText the new start of size text
+ */
+ protected void setSizeStartText(String sizeStartText) {
+ if (sizeStartText == null) {
+ sizeStartText = "";
+ }
+ this.sizeStartText = sizeStartText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the end text to output when a <code>Collection</code>,
+ * <code>Map</code> or array size is output.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of size text
+ */
+ protected String getSizeEndText() {
+ return sizeEndText;
+ }
+
+ /**
+ * <p>Sets the end text to output when a <code>Collection</code>,
+ * <code>Map</code> or array size is output.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param sizeEndText the new end of size text
+ */
+ protected void setSizeEndText(String sizeEndText) {
+ if (sizeEndText == null) {
+ sizeEndText = "";
+ }
+ this.sizeEndText = sizeEndText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the start text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @return the current start of summary text
+ */
+ protected String getSummaryObjectStartText() {
+ return summaryObjectStartText;
+ }
+
+ /**
+ * <p>Sets the start text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectStartText the new start of summary text
+ */
+ protected void setSummaryObjectStartText(String summaryObjectStartText) {
+ if (summaryObjectStartText == null) {
+ summaryObjectStartText = "";
+ }
+ this.summaryObjectStartText = summaryObjectStartText;
+ }
+
+ //---------------------------------------------------------------------
+
+ /**
+ * <p>Gets the end text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of summary text
+ */
+ protected String getSummaryObjectEndText() {
+ return summaryObjectEndText;
+ }
+
+ /**
+ * <p>Sets the end text to output when an <code>Object</code> is
+ * output in summary mode.</p>
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p><code>null</code> is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectEndText the new end of summary text
+ */
+ protected void setSummaryObjectEndText(String summaryObjectEndText) {
+ if (summaryObjectEndText == null) {
+ summaryObjectEndText = "";
+ }
+ this.summaryObjectEndText = summaryObjectEndText;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p>Default <code>ToStringStyle</code>.</p>
+ *
+ * <p>This is an inner class rather than using
+ * <code>StandardToStringStyle</code> to ensure its immutability.</p>
+ */
+ private static final class DefaultToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ DefaultToStringStyle() {
+ super();
+ }
+
+ /**
+ * <p>Ensure <code>Singleton</code> after serialization.</p>
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.DEFAULT_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p><code>ToStringStyle</code> that does not print out
+ * the field names.</p>
+ *
+ * <p>This is an inner class rather than using
+ * <code>StandardToStringStyle</code> to ensure its immutability.
+ */
+ private static final class NoFieldNameToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ NoFieldNameToStringStyle() {
+ super();
+ this.setUseFieldNames(false);
+ }
+
+ /**
+ * <p>Ensure <code>Singleton</code> after serialization.</p>
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.NO_FIELD_NAMES_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p><code>ToStringStyle</code> that prints out the short
+ * class name and no identity hashcode.</p>
+ *
+ * <p>This is an inner class rather than using
+ * <code>StandardToStringStyle</code> to ensure its immutability.</p>
+ */
+ private static final class ShortPrefixToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ ShortPrefixToStringStyle() {
+ super();
+ this.setUseShortClassName(true);
+ this.setUseIdentityHashCode(false);
+ }
+
+ /**
+ * <p>Ensure <code>Singleton</ode> after serialization.</p>
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.SHORT_PREFIX_STYLE;
+ }
+
+ }
+
+ /**
+ * <p><code>ToStringStyle</code> that does not print out the
+ * classname, identity hashcode, content start or field name.</p>
+ *
+ * <p>This is an inner class rather than using
+ * <code>StandardToStringStyle</code> to ensure its immutability.</p>
+ */
+ private static final class SimpleToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ SimpleToStringStyle() {
+ super();
+ this.setUseClassName(false);
+ this.setUseIdentityHashCode(false);
+ this.setUseFieldNames(false);
+ this.setContentStart("");
+ this.setContentEnd("");
+ }
+
+ /**
+ * <p>Ensure <code>Singleton</ode> after serialization.</p>
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.SIMPLE_STYLE;
+ }
+
+ }
+
+ //----------------------------------------------------------------------------
+
+ /**
+ * <p><code>ToStringStyle</code> that outputs on multiple lines.</p>
+ *
+ * <p>This is an inner class rather than using
+ * <code>StandardToStringStyle</code> to ensure its immutability.</p>
+ */
+ private static final class MultiLineToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <p>Constructor.</p>
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ MultiLineToStringStyle() {
+ super();
+ this.setContentStart("[");
+ this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " ");
+ this.setFieldSeparatorAtStart(true);
+ this.setContentEnd(SystemUtils.LINE_SEPARATOR + "]");
+ }
+
+ /**
+ * <p>Ensure <code>Singleton</code> after serialization.</p>
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return ToStringStyle.MULTI_LINE_STYLE;
+ }
+
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/exception/CloneFailedException.java b/src/org/apache/commons/lang3/exception/CloneFailedException.java
new file mode 100644
index 0000000..0c324e9
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/CloneFailedException.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+/**
+ * Exception thrown when a clone cannot be created. In contrast to
+ * {@link CloneNotSupportedException} this is a {@link RuntimeException}.
+ *
+ * @since 3.0
+ */
+public class CloneFailedException extends RuntimeException {
+ // ~ Static fields/initializers ---------------------------------------------
+
+ private static final long serialVersionUID = 20091223L;
+
+ // ~ Constructors -----------------------------------------------------------
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param cause cause of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ * @param cause cause of the exception
+ * @since upcoming
+ */
+ public CloneFailedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/org/apache/commons/lang3/exception/ContextedException.java b/src/org/apache/commons/lang3/exception/ContextedException.java
new file mode 100644
index 0000000..cd517e0
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/ContextedException.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * <p>
+ * An exception that provides an easy and safe way to add contextual information.
+ * </p><p>
+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
+ * Frequently what is needed is a select few pieces of local contextual data.
+ * Providing this data is tricky however, due to concerns over formatting and nulls.
+ * </p><p>
+ * The contexted exception approach allows the exception to be created together with a
+ * list of context label-value pairs. This additional information is automatically included in
+ * the message and printed stack trace.
+ * </p><p>
+ * An unchecked version of this exception is provided by ContextedRuntimeException.
+ * </p>
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (Exception e) {
+ * throw new ContextedException("Error posting account transaction", e)
+ * .addContextValue("Account Number", accountNumber)
+ * .addContextValue("Amount Posted", amountPosted)
+ * .addContextValue("Previous Balance", previousBalance)
+ * }
+ * }
+ * </pre> or improve diagnose data at a higher level:
+ * <pre>
+ * try {
+ * ...
+ * } catch (ContextedException e) {
+ * throw e.setContextValue("Transaction Id", transactionId);
+ * } catch (Exception e) {
+ * if (e instanceof ExceptionContext) {
+ * e.setContextValue("Transaction Id", transactionId);
+ * }
+ * throw e;
+ * }
+ * }
+ * </pre>
+ * </p><p>
+ * The output in a printStacktrace() (which often is written to a log) would look something like the following:
+ * <pre>
+ * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
+ * Exception Context:
+ * [1:Account Number=null]
+ * [2:Amount Posted=100.00]
+ * [3:Previous Balance=-2.17]
+ * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ * ---------------------------------
+ * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ * ..... (rest of trace)
+ * </pre>
+ * </p>
+ *
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public class ContextedException extends Exception implements ExceptionContext {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+ /** The context where the data is stored. */
+ private final ExceptionContext exceptionContext;
+
+ /**
+ * Instantiates ContextedException without message or cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ */
+ public ContextedException() {
+ super();
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with message, but without cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ */
+ public ContextedException(String message) {
+ super(message);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause, but without message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedException(Throwable cause) {
+ super(cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause and message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedException(String message, Throwable cause) {
+ super(message, cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause, message, and ExceptionContext.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ * @param context the context used to store the additional information, null uses default implementation
+ */
+ public ContextedException(String message, Throwable cause, ExceptionContext context) {
+ super(message, cause);
+ if (context == null) {
+ context = new DefaultExceptionContext();
+ }
+ exceptionContext = context;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Different values can be added with the same label multiple times.
+ * <p>
+ * Note: This exception is only serializable if the object added is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ContextedException addContextValue(String label, Object value) {
+ exceptionContext.addContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * Sets information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Any existing values with the same labels are removed before the new one is added.
+ * <p>
+ * Note: This exception is only serializable if the object added as value is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ContextedException setContextValue(String label, Object value) {
+ exceptionContext.setContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Object> getContextValues(String label) {
+ return this.exceptionContext.getContextValues(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getFirstContextValue(String label) {
+ return this.exceptionContext.getFirstContextValue(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Pair<String, Object>> getContextEntries() {
+ return this.exceptionContext.getContextEntries();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<String> getContextLabels() {
+ return exceptionContext.getContextLabels();
+ }
+
+ /**
+ * Provides the message explaining the exception, including the contextual data.
+ *
+ * @see java.lang.Throwable#getMessage()
+ * @return the message, never null
+ */
+ @Override
+ public String getMessage(){
+ return getFormattedExceptionMessage(super.getMessage());
+ }
+
+ /**
+ * Provides the message explaining the exception without the contextual data.
+ *
+ * @see java.lang.Throwable#getMessage()
+ * @return the message
+ * @since 3.0.1
+ */
+ public String getRawMessage() {
+ return super.getMessage();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getFormattedExceptionMessage(String baseMessage) {
+ return exceptionContext.getFormattedExceptionMessage(baseMessage);
+ }
+}
diff --git a/src/org/apache/commons/lang3/exception/ContextedRuntimeException.java b/src/org/apache/commons/lang3/exception/ContextedRuntimeException.java
new file mode 100644
index 0000000..85e035e
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/ContextedRuntimeException.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * <p>
+ * A runtime exception that provides an easy and safe way to add contextual information.
+ * </p><p>
+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
+ * Frequently what is needed is a select few pieces of local contextual data.
+ * Providing this data is tricky however, due to concerns over formatting and nulls.
+ * </p><p>
+ * The contexted exception approach allows the exception to be created together with a
+ * list of context label-value pairs. This additional information is automatically included in
+ * the message and printed stack trace.
+ * </p><p>
+ * A checked version of this exception is provided by ContextedException.
+ * </p>
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (Exception e) {
+ * throw new ContextedRuntimeException("Error posting account transaction", e)
+ * .addContextValue("Account Number", accountNumber)
+ * .addContextValue("Amount Posted", amountPosted)
+ * .addContextValue("Previous Balance", previousBalance)
+ * }
+ * }
+ * </pre> or improve diagnose data at a higher level:
+ * <pre>
+ * try {
+ * ...
+ * } catch (ContextedRuntimeException e) {
+ * throw e.setContextValue("Transaction Id", transactionId);
+ * } catch (Exception e) {
+ * if (e instanceof ExceptionContext) {
+ * e.setContextValue("Transaction Id", transactionId);
+ * }
+ * throw e;
+ * }
+ * }
+ * </pre>
+ * </p><p>
+ * The output in a printStacktrace() (which often is written to a log) would look something like the following:
+ * <pre>
+ * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
+ * Exception Context:
+ * [1:Account Number=null]
+ * [2:Amount Posted=100.00]
+ * [3:Previous Balance=-2.17]
+ * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ * ---------------------------------
+ * at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ * ..... (rest of trace)
+ * </pre>
+ * </p>
+ *
+ * @see ContextedException
+ * @since 3.0
+ */
+public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+ /** The context where the data is stored. */
+ private final ExceptionContext exceptionContext;
+
+ /**
+ * Instantiates ContextedRuntimeException without message or cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ */
+ public ContextedRuntimeException() {
+ super();
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with message, but without cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ */
+ public ContextedRuntimeException(String message) {
+ super(message);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause, but without message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedRuntimeException(Throwable cause) {
+ super(cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause and message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ * @param context the context used to store the additional information, null uses default implementation
+ */
+ public ContextedRuntimeException(String message, Throwable cause, ExceptionContext context) {
+ super(message, cause);
+ if (context == null) {
+ context = new DefaultExceptionContext();
+ }
+ exceptionContext = context;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Different values can be added with the same label multiple times.
+ * <p>
+ * Note: This exception is only serializable if the object added is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ContextedRuntimeException addContextValue(String label, Object value) {
+ exceptionContext.addContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * Sets information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Any existing values with the same labels are removed before the new one is added.
+ * <p>
+ * Note: This exception is only serializable if the object added as value is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ContextedRuntimeException setContextValue(String label, Object value) {
+ exceptionContext.setContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Object> getContextValues(String label) {
+ return this.exceptionContext.getContextValues(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getFirstContextValue(String label) {
+ return this.exceptionContext.getFirstContextValue(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Pair<String, Object>> getContextEntries() {
+ return this.exceptionContext.getContextEntries();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<String> getContextLabels() {
+ return exceptionContext.getContextLabels();
+ }
+
+ /**
+ * Provides the message explaining the exception, including the contextual data.
+ *
+ * @see java.lang.Throwable#getMessage()
+ * @return the message, never null
+ */
+ @Override
+ public String getMessage(){
+ return getFormattedExceptionMessage(super.getMessage());
+ }
+
+ /**
+ * Provides the message explaining the exception without the contextual data.
+ *
+ * @see java.lang.Throwable#getMessage()
+ * @return the message
+ * @since 3.0.1
+ */
+ public String getRawMessage() {
+ return super.getMessage();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getFormattedExceptionMessage(String baseMessage) {
+ return exceptionContext.getFormattedExceptionMessage(baseMessage);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java
new file mode 100644
index 0000000..3011f8b
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Default implementation of the context storing the label-value pairs for contexted exceptions.
+ * <p>
+ * This implementation is serializable, however this is dependent on the values that
+ * are added also being serializable.
+ * </p>
+ *
+ * @see ContextedException
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public class DefaultExceptionContext implements ExceptionContext, Serializable {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+
+ /** The list storing the label-data pairs. */
+ private final List<Pair<String, Object>> contextValues = new ArrayList<Pair<String,Object>>();
+
+ /**
+ * {@inheritDoc}
+ */
+ public DefaultExceptionContext addContextValue(String label, Object value) {
+ contextValues.add(new ImmutablePair<String, Object>(label, value));
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public DefaultExceptionContext setContextValue(String label, Object value) {
+ for (final Iterator<Pair<String, Object>> iter = contextValues.iterator(); iter.hasNext();) {
+ final Pair<String, Object> p = iter.next();
+ if (StringUtils.equals(label, p.getKey())) {
+ iter.remove();
+ }
+ }
+ addContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Object> getContextValues(String label) {
+ final List<Object> values = new ArrayList<Object>();
+ for (final Pair<String, Object> pair : contextValues) {
+ if (StringUtils.equals(label, pair.getKey())) {
+ values.add(pair.getValue());
+ }
+ }
+ return values;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getFirstContextValue(String label) {
+ for (final Pair<String, Object> pair : contextValues) {
+ if (StringUtils.equals(label, pair.getKey())) {
+ return pair.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<String> getContextLabels() {
+ final Set<String> labels = new HashSet<String>();
+ for (final Pair<String, Object> pair : contextValues) {
+ labels.add(pair.getKey());
+ }
+ return labels;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<Pair<String, Object>> getContextEntries() {
+ return contextValues;
+ }
+
+ /**
+ * Builds the message containing the contextual information.
+ *
+ * @param baseMessage the base exception message <b>without</b> context information appended
+ * @return the exception message <b>with</b> context information appended, never null
+ */
+ public String getFormattedExceptionMessage(String baseMessage){
+ StringBuilder buffer = new StringBuilder(256);
+ if (baseMessage != null) {
+ buffer.append(baseMessage);
+ }
+
+ if (contextValues.size() > 0) {
+ if (buffer.length() > 0) {
+ buffer.append('\n');
+ }
+ buffer.append("Exception Context:\n");
+
+ int i = 0;
+ for (final Pair<String, Object> pair : contextValues) {
+ buffer.append("\t[");
+ buffer.append(++i);
+ buffer.append(':');
+ buffer.append(pair.getKey());
+ buffer.append("=");
+ final Object value = pair.getValue();
+ if (value == null) {
+ buffer.append("null");
+ } else {
+ String valueStr;
+ try {
+ valueStr = value.toString();
+ } catch (Exception e) {
+ valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e);
+ }
+ buffer.append(valueStr);
+ }
+ buffer.append("]\n");
+ }
+ buffer.append("---------------------------------");
+ }
+ return buffer.toString();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/exception/ExceptionContext.java b/src/org/apache/commons/lang3/exception/ExceptionContext.java
new file mode 100644
index 0000000..d8076c8
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/ExceptionContext.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Allows the storage and retrieval of contextual information based on label-value
+ * pairs for exceptions.
+ * <p>
+ * Implementations are expected to manage the pairs in a list-style collection
+ * that keeps the pairs in the sequence of their addition.
+ * </p>
+ *
+ * @see ContextedException
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public interface ExceptionContext {
+
+ /**
+ * Adds a contextual label-value pair into this context.
+ * <p>
+ * The pair will be added to the context, independently of an already
+ * existing pair with the same label.
+ * </p>
+ *
+ * @param label the label of the item to add, {@code null} not recommended
+ * @param value the value of item to add, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ExceptionContext addContextValue(String label, Object value);
+
+ /**
+ * Sets a contextual label-value pair into this context.
+ * <p>
+ * The pair will be added normally, but any existing label-value pair with
+ * the same label is removed from the context.
+ * </p>
+ *
+ * @param label the label of the item to add, {@code null} not recommended
+ * @param value the value of item to add, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ public ExceptionContext setContextValue(String label, Object value);
+
+ /**
+ * Retrieves all the contextual data values associated with the label.
+ *
+ * @param label the label to get the contextual values for, may be {@code null}
+ * @return the contextual values associated with the label, never {@code null}
+ */
+ public List<Object> getContextValues(String label);
+
+ /**
+ * Retrieves the first available contextual data value associated with the label.
+ *
+ * @param label the label to get the contextual value for, may be {@code null}
+ * @return the first contextual value associated with the label, may be {@code null}
+ */
+ public Object getFirstContextValue(String label);
+
+ /**
+ * Retrieves the full set of labels defined in the contextual data.
+ *
+ * @return the set of labels, not {@code null}
+ */
+ public Set<String> getContextLabels();
+
+ /**
+ * Retrieves the full list of label-value pairs defined in the contextual data.
+ *
+ * @return the list of pairs, not {@code null}
+ */
+ public List<Pair<String, Object>> getContextEntries();
+
+ /**
+ * Gets the contextualized error message based on a base message.
+ * This will add the context label-value pairs to the message.
+ *
+ * @param baseMessage the base exception message <b>without</b> context information appended
+ * @return the exception message <b>with</b> context information appended, not {@code null}
+ */
+ public String getFormattedExceptionMessage(String baseMessage);
+
+}
diff --git a/src/org/apache/commons/lang3/exception/ExceptionUtils.java b/src/org/apache/commons/lang3/exception/ExceptionUtils.java
new file mode 100644
index 0000000..68d3e53
--- /dev/null
+++ b/src/org/apache/commons/lang3/exception/ExceptionUtils.java
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.exception;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * <p>Provides utilities for manipulating and examining
+ * <code>Throwable</code> objects.</p>
+ *
+ * @since 1.0
+ * @version $Id: ExceptionUtils.java 1144929 2011-07-10 18:26:16Z ggregory $
+ */
+public class ExceptionUtils {
+
+ /**
+ * <p>Used when printing stack frames to denote the start of a
+ * wrapped exception.</p>
+ *
+ * <p>Package private for accessibility by test suite.</p>
+ */
+ static final String WRAPPED_MARKER = " [wrapped] ";
+
+ /**
+ * <p>The names of methods commonly used to access a wrapped exception.</p>
+ */
+ // TODO: Remove in Lang 4.0
+ private static final String[] CAUSE_METHOD_NAMES = {
+ "getCause",
+ "getNextException",
+ "getTargetException",
+ "getException",
+ "getSourceException",
+ "getRootCause",
+ "getCausedByException",
+ "getNested",
+ "getLinkedException",
+ "getNestedException",
+ "getLinkedCause",
+ "getThrowable",
+ };
+
+ /**
+ * <p>
+ * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
+ * normally necessary.
+ * </p>
+ */
+ public ExceptionUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns the default names used when searching for the cause of an exception.</p>
+ *
+ * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
+ *
+ * @return cloned array of the default method names
+ * @since 3.0
+ * @deprecated This feature will be removed in Lang 4.0
+ */
+ @Deprecated
+ public static String[] getDefaultCauseMethodNames() {
+ return ArrayUtils.clone(CAUSE_METHOD_NAMES);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
+ *
+ * <p>The method searches for methods with specific names that return a
+ * <code>Throwable</code> object. This will pick up most wrapping exceptions,
+ * including those from JDK 1.4.
+ *
+ * <p>The default list searched for are:</p>
+ * <ul>
+ * <li><code>getCause()</code></li>
+ * <li><code>getNextException()</code></li>
+ * <li><code>getTargetException()</code></li>
+ * <li><code>getException()</code></li>
+ * <li><code>getSourceException()</code></li>
+ * <li><code>getRootCause()</code></li>
+ * <li><code>getCausedByException()</code></li>
+ * <li><code>getNested()</code></li>
+ * </ul>
+ *
+ * <p>If none of the above is found, returns <code>null</code>.</p>
+ *
+ * @param throwable the throwable to introspect for a cause, may be null
+ * @return the cause of the <code>Throwable</code>,
+ * <code>null</code> if none found or null throwable input
+ * @since 1.0
+ * @deprecated This feature will be removed in Lang 4.0
+ */
+ @Deprecated
+ public static Throwable getCause(Throwable throwable) {
+ return getCause(throwable, CAUSE_METHOD_NAMES);
+ }
+
+ /**
+ * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
+ *
+ * <p>A <code>null</code> set of method names means use the default set.
+ * A <code>null</code> in the set of method names will be ignored.</p>
+ *
+ * @param throwable the throwable to introspect for a cause, may be null
+ * @param methodNames the method names, null treated as default set
+ * @return the cause of the <code>Throwable</code>,
+ * <code>null</code> if none found or null throwable input
+ * @since 1.0
+ * @deprecated This feature will be removed in Lang 4.0
+ */
+ @Deprecated
+ public static Throwable getCause(Throwable throwable, String[] methodNames) {
+ if (throwable == null) {
+ return null;
+ }
+
+ if (methodNames == null) {
+ methodNames = CAUSE_METHOD_NAMES;
+ }
+
+ for (String methodName : methodNames) {
+ if (methodName != null) {
+ Throwable cause = getCauseUsingMethodName(throwable, methodName);
+ if (cause != null) {
+ return cause;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
+ *
+ * <p>This method walks through the exception chain to the last element,
+ * "root" of the tree, using {@link #getCause(Throwable)}, and
+ * returns that exception.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. If the throwable parameter
+ * has a cause of itself, then null will be returned. If the throwable
+ * parameter cause chain loops, the last element in the chain before the
+ * loop is returned.</p>
+ *
+ * @param throwable the throwable to get the root cause for, may be null
+ * @return the root cause of the <code>Throwable</code>,
+ * <code>null</code> if none found or null throwable input
+ */
+ public static Throwable getRootCause(Throwable throwable) {
+ List<Throwable> list = getThrowableList(throwable);
+ return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1));
+ }
+
+ /**
+ * <p>Finds a <code>Throwable</code> by method name.</p>
+ *
+ * @param throwable the exception to examine
+ * @param methodName the name of the method to find and invoke
+ * @return the wrapped exception, or <code>null</code> if not found
+ */
+ // TODO: Remove in Lang 4.0
+ private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
+ Method method = null;
+ try {
+ method = throwable.getClass().getMethod(methodName);
+ } catch (NoSuchMethodException ignored) { // NOPMD
+ // exception ignored
+ } catch (SecurityException ignored) { // NOPMD
+ // exception ignored
+ }
+
+ if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
+ try {
+ return (Throwable) method.invoke(throwable);
+ } catch (IllegalAccessException ignored) { // NOPMD
+ // exception ignored
+ } catch (IllegalArgumentException ignored) { // NOPMD
+ // exception ignored
+ } catch (InvocationTargetException ignored) { // NOPMD
+ // exception ignored
+ }
+ }
+ return null;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Counts the number of <code>Throwable</code> objects in the
+ * exception chain.</p>
+ *
+ * <p>A throwable without cause will return <code>1</code>.
+ * A throwable with one cause will return <code>2</code> and so on.
+ * A <code>null</code> throwable will return <code>0</code>.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the
+ * chain is already in the result set.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @return the count of throwables, zero if null input
+ */
+ public static int getThrowableCount(Throwable throwable) {
+ return getThrowableList(throwable).size();
+ }
+
+ /**
+ * <p>Returns the list of <code>Throwable</code> objects in the
+ * exception chain.</p>
+ *
+ * <p>A throwable without cause will return an array containing
+ * one element - the input throwable.
+ * A throwable with one cause will return an array containing
+ * two elements. - the input throwable and the cause throwable.
+ * A <code>null</code> throwable will return an array of size zero.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the
+ * chain is already in the result set.</p>
+ *
+ * @see #getThrowableList(Throwable)
+ * @param throwable the throwable to inspect, may be null
+ * @return the array of throwables, never null
+ */
+ public static Throwable[] getThrowables(Throwable throwable) {
+ List<Throwable> list = getThrowableList(throwable);
+ return list.toArray(new Throwable[list.size()]);
+ }
+
+ /**
+ * <p>Returns the list of <code>Throwable</code> objects in the
+ * exception chain.</p>
+ *
+ * <p>A throwable without cause will return a list containing
+ * one element - the input throwable.
+ * A throwable with one cause will return a list containing
+ * two elements. - the input throwable and the cause throwable.
+ * A <code>null</code> throwable will return a list of size zero.</p>
+ *
+ * <p>This method handles recursive cause structures that might
+ * otherwise cause infinite loops. The cause chain is processed until
+ * the end is reached, or until the next item in the chain is already
+ * in the result set.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @return the list of throwables, never null
+ * @since Commons Lang 2.2
+ */
+ public static List<Throwable> getThrowableList(Throwable throwable) {
+ List<Throwable> list = new ArrayList<Throwable>();
+ while (throwable != null && list.contains(throwable) == false) {
+ list.add(throwable);
+ throwable = ExceptionUtils.getCause(throwable);
+ }
+ return list;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns the (zero based) index of the first <code>Throwable</code>
+ * that matches the specified class (exactly) in the exception chain.
+ * Subclasses of the specified class do not match - see
+ * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
+ *
+ * <p>A <code>null</code> throwable returns <code>-1</code>.
+ * A <code>null</code> type returns <code>-1</code>.
+ * No match in the chain returns <code>-1</code>.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ */
+ public static int indexOfThrowable(Throwable throwable, Class<?> clazz) {
+ return indexOf(throwable, clazz, 0, false);
+ }
+
+ /**
+ * <p>Returns the (zero based) index of the first <code>Throwable</code>
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do not match - see
+ * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
+ *
+ * <p>A <code>null</code> throwable returns <code>-1</code>.
+ * A <code>null</code> type returns <code>-1</code>.
+ * No match in the chain returns <code>-1</code>.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns <code>-1</code>.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns -1
+ * @param fromIndex the (zero based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ */
+ public static int indexOfThrowable(Throwable throwable, Class<?> clazz, int fromIndex) {
+ return indexOf(throwable, clazz, fromIndex, false);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns the (zero based) index of the first <code>Throwable</code>
+ * that matches the specified class or subclass in the exception chain.
+ * Subclasses of the specified class do match - see
+ * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+ *
+ * <p>A <code>null</code> throwable returns <code>-1</code>.
+ * A <code>null</code> type returns <code>-1</code>.
+ * No match in the chain returns <code>-1</code>.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ * @since 2.1
+ */
+ public static int indexOfType(Throwable throwable, Class<?> type) {
+ return indexOf(throwable, type, 0, true);
+ }
+
+ /**
+ * <p>Returns the (zero based) index of the first <code>Throwable</code>
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do match - see
+ * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+ *
+ * <p>A <code>null</code> throwable returns <code>-1</code>.
+ * A <code>null</code> type returns <code>-1</code>.
+ * No match in the chain returns <code>-1</code>.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns <code>-1</code>.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @param fromIndex the (zero based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ * @since 2.1
+ */
+ public static int indexOfType(Throwable throwable, Class<?> type, int fromIndex) {
+ return indexOf(throwable, type, fromIndex, true);
+ }
+
+ /**
+ * <p>Worker method for the <code>indexOfType</code> methods.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @param fromIndex the (zero based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
+ * using references
+ * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code>
+ */
+ private static int indexOf(Throwable throwable, Class<?> type, int fromIndex, boolean subclass) {
+ if (throwable == null || type == null) {
+ return -1;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
+ if (fromIndex >= throwables.length) {
+ return -1;
+ }
+ if (subclass) {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.isAssignableFrom(throwables[i].getClass())) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.equals(throwables[i].getClass())) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Prints a compact stack trace for the root cause of a throwable
+ * to <code>System.err</code>.</p>
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to <code>printStackTrace</code> for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output
+ * @since 2.0
+ */
+ public static void printRootCauseStackTrace(Throwable throwable) {
+ printRootCauseStackTrace(throwable, System.err);
+ }
+
+ /**
+ * <p>Prints a compact stack trace for the root cause of a throwable.</p>
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to <code>printStackTrace</code> for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output, may be null
+ * @param stream the stream to output to, may not be null
+ * @throws IllegalArgumentException if the stream is <code>null</code>
+ * @since 2.0
+ */
+ public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) {
+ if (throwable == null) {
+ return;
+ }
+ if (stream == null) {
+ throw new IllegalArgumentException("The PrintStream must not be null");
+ }
+ String trace[] = getRootCauseStackTrace(throwable);
+ for (String element : trace) {
+ stream.println(element);
+ }
+ stream.flush();
+ }
+
+ /**
+ * <p>Prints a compact stack trace for the root cause of a throwable.</p>
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to <code>printStackTrace</code> for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output, may be null
+ * @param writer the writer to output to, may not be null
+ * @throws IllegalArgumentException if the writer is <code>null</code>
+ * @since 2.0
+ */
+ public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) {
+ if (throwable == null) {
+ return;
+ }
+ if (writer == null) {
+ throw new IllegalArgumentException("The PrintWriter must not be null");
+ }
+ String trace[] = getRootCauseStackTrace(throwable);
+ for (String element : trace) {
+ writer.println(element);
+ }
+ writer.flush();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Creates a compact stack trace for the root cause of the supplied
+ * <code>Throwable</code>.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * It consists of the root exception followed by each of its wrapping
+ * exceptions separated by '[wrapped]'. Note that this is the opposite
+ * order to the JDK1.4 display.</p>
+ *
+ * @param throwable the throwable to examine, may be null
+ * @return an array of stack trace frames, never null
+ * @since 2.0
+ */
+ public static String[] getRootCauseStackTrace(Throwable throwable) {
+ if (throwable == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ Throwable throwables[] = getThrowables(throwable);
+ int count = throwables.length;
+ List<String> frames = new ArrayList<String>();
+ List<String> nextTrace = getStackFrameList(throwables[count - 1]);
+ for (int i = count; --i >= 0;) {
+ List<String> trace = nextTrace;
+ if (i != 0) {
+ nextTrace = getStackFrameList(throwables[i - 1]);
+ removeCommonFrames(trace, nextTrace);
+ }
+ if (i == count - 1) {
+ frames.add(throwables[i].toString());
+ } else {
+ frames.add(WRAPPED_MARKER + throwables[i].toString());
+ }
+ for (int j = 0; j < trace.size(); j++) {
+ frames.add(trace.get(j));
+ }
+ }
+ return frames.toArray(new String[frames.size()]);
+ }
+
+ /**
+ * <p>Removes common frames from the cause trace given the two stack traces.</p>
+ *
+ * @param causeFrames stack trace of a cause throwable
+ * @param wrapperFrames stack trace of a wrapper throwable
+ * @throws IllegalArgumentException if either argument is null
+ * @since 2.0
+ */
+ public static void removeCommonFrames(List<String> causeFrames, List<String> wrapperFrames) {
+ if (causeFrames == null || wrapperFrames == null) {
+ throw new IllegalArgumentException("The List must not be null");
+ }
+ int causeFrameIndex = causeFrames.size() - 1;
+ int wrapperFrameIndex = wrapperFrames.size() - 1;
+ while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
+ // Remove the frame from the cause trace if it is the same
+ // as in the wrapper trace
+ String causeFrame = causeFrames.get(causeFrameIndex);
+ String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
+ if (causeFrame.equals(wrapperFrame)) {
+ causeFrames.remove(causeFrameIndex);
+ }
+ causeFrameIndex--;
+ wrapperFrameIndex--;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the stack trace from a Throwable as a String.</p>
+ *
+ * <p>The result of this method vary by JDK version as this method
+ * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+ * On JDK1.3 and earlier, the cause exception will not be shown
+ * unless the specified throwable alters printStackTrace.</p>
+ *
+ * @param throwable the <code>Throwable</code> to be examined
+ * @return the stack trace as generated by the exception's
+ * <code>printStackTrace(PrintWriter)</code> method
+ */
+ public static String getStackTrace(Throwable throwable) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw, true);
+ throwable.printStackTrace(pw);
+ return sw.getBuffer().toString();
+ }
+
+ /**
+ * <p>Captures the stack trace associated with the specified
+ * <code>Throwable</code> object, decomposing it into a list of
+ * stack frames.</p>
+ *
+ * <p>The result of this method vary by JDK version as this method
+ * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+ * On JDK1.3 and earlier, the cause exception will not be shown
+ * unless the specified throwable alters printStackTrace.</p>
+ *
+ * @param throwable the <code>Throwable</code> to examine, may be null
+ * @return an array of strings describing each stack frame, never null
+ */
+ public static String[] getStackFrames(Throwable throwable) {
+ if (throwable == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ return getStackFrames(getStackTrace(throwable));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns an array where each element is a line from the argument.</p>
+ *
+ * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
+ *
+ * @param stackTrace a stack trace String
+ * @return an array where each element is a line from the argument
+ */
+ static String[] getStackFrames(String stackTrace) {
+ String linebreak = SystemUtils.LINE_SEPARATOR;
+ StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ List<String> list = new ArrayList<String>();
+ while (frames.hasMoreTokens()) {
+ list.add(frames.nextToken());
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * <p>Produces a <code>List</code> of stack frames - the message
+ * is not included. Only the trace of the specified exception is
+ * returned, any caused by trace is stripped.</p>
+ *
+ * <p>This works in most cases - it will only fail if the exception
+ * message contains a line that starts with:
+ * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
+ *
+ * @param t is any throwable
+ * @return List of stack frames
+ */
+ static List<String> getStackFrameList(Throwable t) {
+ String stackTrace = getStackTrace(t);
+ String linebreak = SystemUtils.LINE_SEPARATOR;
+ StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ List<String> list = new ArrayList<String>();
+ boolean traceStarted = false;
+ while (frames.hasMoreTokens()) {
+ String token = frames.nextToken();
+ // Determine if the line starts with <whitespace>at
+ int at = token.indexOf("at");
+ if (at != -1 && token.substring(0, at).trim().length() == 0) {
+ traceStarted = true;
+ list.add(token);
+ } else if (traceStarted) {
+ break;
+ }
+ }
+ return list;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets a short message summarising the exception.
+ * <p>
+ * The message returned is of the form
+ * {ClassNameWithoutPackage}: {ThrowableMessage}
+ *
+ * @param th the throwable to get a message for, null returns empty string
+ * @return the message, non-null
+ * @since Commons Lang 2.2
+ */
+ public static String getMessage(Throwable th) {
+ if (th == null) {
+ return "";
+ }
+ String clsName = ClassUtils.getShortClassName(th, null);
+ String msg = th.getMessage();
+ return clsName + ": " + StringUtils.defaultString(msg);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets a short message summarising the root cause exception.
+ * <p>
+ * The message returned is of the form
+ * {ClassNameWithoutPackage}: {ThrowableMessage}
+ *
+ * @param th the throwable to get a message for, null returns empty string
+ * @return the message, non-null
+ * @since Commons Lang 2.2
+ */
+ public static String getRootCauseMessage(Throwable th) {
+ Throwable root = ExceptionUtils.getRootCause(th);
+ root = (root == null ? th : root);
+ return getMessage(root);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/Mutable.java b/src/org/apache/commons/lang3/mutable/Mutable.java
new file mode 100644
index 0000000..64514f0
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/Mutable.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * Provides mutable access to a value.
+ * <p>
+ * <code>Mutable</code> is used as a generic interface to the implementations in this package.
+ * <p>
+ * A typical use case would be to enable a primitive or string to be passed to a method and allow that method to
+ * effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in
+ * a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects.
+ *
+ * @since 2.1
+ * @param <T> the type to set and get
+ * @version $Id: Mutable.java 1153213 2011-08-02 17:35:39Z ggregory $
+ */
+public interface Mutable<T> {
+
+ /**
+ * Gets the value of this mutable.
+ *
+ * @return the stored value
+ */
+ T getValue();
+
+ /**
+ * Sets the value of this mutable.
+ *
+ * @param value
+ * the value to store
+ * @throws NullPointerException
+ * if the object is null and null is invalid
+ * @throws ClassCastException
+ * if the type is invalid
+ */
+ void setValue(T value);
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableBoolean.java b/src/org/apache/commons/lang3/mutable/MutableBoolean.java
new file mode 100644
index 0000000..f2e0a48
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableBoolean.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+import java.io.Serializable;
+
+/**
+ * A mutable <code>boolean</code> wrapper.
+ * <p>
+ * Note that as MutableBoolean does not extend Boolean, it is not treated by String.format as a Boolean parameter.
+ *
+ * @see Boolean
+ * @since 2.2
+ * @version $Id: MutableBoolean.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableBoolean implements Mutable<Boolean>, Serializable, Comparable<MutableBoolean> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -4830728138360036487L;
+
+ /** The mutable value. */
+ private boolean value;
+
+ /**
+ * Constructs a new MutableBoolean with the default value of false.
+ */
+ public MutableBoolean() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableBoolean with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableBoolean(boolean value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableBoolean with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableBoolean(Boolean value) {
+ super();
+ this.value = value.booleanValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Boolean instance.
+ *
+ * @return the value as a Boolean, never null
+ */
+ public Boolean getValue() {
+ return Boolean.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(boolean value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Boolean instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Boolean value) {
+ this.value = value.booleanValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the current value is <code>true</code>.
+ *
+ * @return <code>true</code> if the current value is <code>true</code>
+ * @since 2.5
+ */
+ public boolean isTrue() {
+ return value == true;
+ }
+
+ /**
+ * Checks if the current value is <code>false</code>.
+ *
+ * @return <code>true</code> if the current value is <code>false</code>
+ * @since 2.5
+ */
+ public boolean isFalse() {
+ return value == false;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the value of this MutableBoolean as a boolean.
+ *
+ * @return the boolean value represented by this object.
+ */
+ public boolean booleanValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Boolean.
+ *
+ * @return a Boolean instance containing the value from this mutable, never null
+ * @since 2.5
+ */
+ public Boolean toBoolean() {
+ return Boolean.valueOf(booleanValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is <code>true</code> if and only if the argument is
+ * not <code>null</code> and is an <code>MutableBoolean</code> object that contains the same
+ * <code>boolean</code> value as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableBoolean) {
+ return value == ((MutableBoolean) obj).booleanValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return the hash code returned by <code>Boolean.TRUE</code> or <code>Boolean.FALSE</code>
+ */
+ @Override
+ public int hashCode() {
+ return value ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ * where false is less than true
+ */
+ public int compareTo(MutableBoolean other) {
+ boolean anotherVal = other.value;
+ return value == anotherVal ? 0 : (value ? 1 : -1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableByte.java b/src/org/apache/commons/lang3/mutable/MutableByte.java
new file mode 100644
index 0000000..129d86d
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableByte.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>byte</code> wrapper.
+ * <p>
+ * Note that as MutableByte does not extend Byte, it is not treated by String.format as a Byte parameter.
+ *
+ * @see Byte
+ * @since 2.1
+ * @version $Id: MutableByte.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableByte extends Number implements Comparable<MutableByte>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -1585823265L;
+
+ /** The mutable value. */
+ private byte value;
+
+ /**
+ * Constructs a new MutableByte with the default value of zero.
+ */
+ public MutableByte() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableByte with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableByte(byte value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableByte with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableByte(Number value) {
+ super();
+ this.value = value.byteValue();
+ }
+
+ /**
+ * Constructs a new MutableByte parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a byte
+ * @since 2.5
+ */
+ public MutableByte(String value) throws NumberFormatException {
+ super();
+ this.value = Byte.parseByte(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Byte instance.
+ *
+ * @return the value as a Byte, never null
+ */
+ public Byte getValue() {
+ return Byte.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(byte value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.byteValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(byte operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.byteValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(byte operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.byteValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue relies on Number implementation
+ /**
+ * Returns the value of this MutableByte as a byte.
+ *
+ * @return the numeric value represented by this object after conversion to type byte.
+ */
+ @Override
+ public byte byteValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Byte.
+ *
+ * @return a Byte instance containing the value from this mutable
+ */
+ public Byte toByte() {
+ return Byte.valueOf(byteValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is <code>true</code> if and only if the argument is
+ * not <code>null</code> and is a <code>MutableByte</code> object that contains the same <code>byte</code> value
+ * as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableByte) {
+ return value == ((MutableByte) obj).byteValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableByte other) {
+ byte anotherVal = other.value;
+ return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableDouble.java b/src/org/apache/commons/lang3/mutable/MutableDouble.java
new file mode 100644
index 0000000..f4acfa9
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableDouble.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>double</code> wrapper.
+ * <p>
+ * Note that as MutableDouble does not extend Double, it is not treated by String.format as a Double parameter.
+ *
+ * @see Double
+ * @since 2.1
+ * @version $Id: MutableDouble.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableDouble extends Number implements Comparable<MutableDouble>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1587163916L;
+
+ /** The mutable value. */
+ private double value;
+
+ /**
+ * Constructs a new MutableDouble with the default value of zero.
+ */
+ public MutableDouble() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableDouble with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableDouble(double value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableDouble with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableDouble(Number value) {
+ super();
+ this.value = value.doubleValue();
+ }
+
+ /**
+ * Constructs a new MutableDouble parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a double
+ * @since 2.5
+ */
+ public MutableDouble(String value) throws NumberFormatException {
+ super();
+ this.value = Double.parseDouble(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Double instance.
+ *
+ * @return the value as a Double, never null
+ */
+ public Double getValue() {
+ return Double.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.doubleValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks whether the double value is the special NaN value.
+ *
+ * @return true if NaN
+ */
+ public boolean isNaN() {
+ return Double.isNaN(value);
+ }
+
+ /**
+ * Checks whether the double value is infinite.
+ *
+ * @return true if infinite
+ */
+ public boolean isInfinite() {
+ return Double.isInfinite(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add
+ * @since Commons Lang 2.2
+ */
+ public void add(double operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.doubleValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(double operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.doubleValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableDouble as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * Returns the value of this MutableDouble as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return (long) value;
+ }
+
+ /**
+ * Returns the value of this MutableDouble as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return (float) value;
+ }
+
+ /**
+ * Returns the value of this MutableDouble as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Double.
+ *
+ * @return a Double instance containing the value from this mutable, never null
+ */
+ public Double toDouble() {
+ return Double.valueOf(doubleValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object against the specified object. The result is <code>true</code> if and only if the argument
+ * is not <code>null</code> and is a <code>Double</code> object that represents a double that has the identical
+ * bit pattern to the bit pattern of the double represented by this object. For this purpose, two
+ * <code>double</code> values are considered to be the same if and only if the method
+ * {@link Double#doubleToLongBits(double)}returns the same long value when applied to each.
+ * <p>
+ * Note that in most cases, for two instances of class <code>Double</code>,<code>d1</code> and <code>d2</code>,
+ * the value of <code>d1.equals(d2)</code> is <code>true</code> if and only if <blockquote>
+ *
+ * <pre>
+ * d1.doubleValue()&nbsp;== d2.doubleValue()
+ * </pre>
+ *
+ * </blockquote>
+ * <p>
+ * also has the value <code>true</code>. However, there are two exceptions:
+ * <ul>
+ * <li>If <code>d1</code> and <code>d2</code> both represent <code>Double.NaN</code>, then the
+ * <code>equals</code> method returns <code>true</code>, even though <code>Double.NaN==Double.NaN</code> has
+ * the value <code>false</code>.
+ * <li>If <code>d1</code> represents <code>+0.0</code> while <code>d2</code> represents <code>-0.0</code>,
+ * or vice versa, the <code>equal</code> test has the value <code>false</code>, even though
+ * <code>+0.0==-0.0</code> has the value <code>true</code>. This allows hashtables to operate properly.
+ * </ul>
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof MutableDouble)
+ && (Double.doubleToLongBits(((MutableDouble) obj).value) == Double.doubleToLongBits(value));
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ long bits = Double.doubleToLongBits(value);
+ return (int) (bits ^ (bits >>> 32));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableDouble other) {
+ double anotherVal = other.value;
+ return Double.compare(value, anotherVal);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableFloat.java b/src/org/apache/commons/lang3/mutable/MutableFloat.java
new file mode 100644
index 0000000..b7e0cd0
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableFloat.java
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>float</code> wrapper.
+ * <p>
+ * Note that as MutableFloat does not extend Float, it is not treated by String.format as a Float parameter.
+ *
+ * @see Float
+ * @since 2.1
+ * @version $Id: MutableFloat.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableFloat extends Number implements Comparable<MutableFloat>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 5787169186L;
+
+ /** The mutable value. */
+ private float value;
+
+ /**
+ * Constructs a new MutableFloat with the default value of zero.
+ */
+ public MutableFloat() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableFloat with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableFloat(float value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableFloat with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableFloat(Number value) {
+ super();
+ this.value = value.floatValue();
+ }
+
+ /**
+ * Constructs a new MutableFloat parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a float
+ * @since 2.5
+ */
+ public MutableFloat(String value) throws NumberFormatException {
+ super();
+ this.value = Float.parseFloat(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Float instance.
+ *
+ * @return the value as a Float, never null
+ */
+ public Float getValue() {
+ return Float.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(float value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.floatValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks whether the float value is the special NaN value.
+ *
+ * @return true if NaN
+ */
+ public boolean isNaN() {
+ return Float.isNaN(value);
+ }
+
+ /**
+ * Checks whether the float value is infinite.
+ *
+ * @return true if infinite
+ */
+ public boolean isInfinite() {
+ return Float.isInfinite(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(float operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.floatValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract
+ * @since Commons Lang 2.2
+ */
+ public void subtract(float operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.floatValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableFloat as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return (long) value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Float.
+ *
+ * @return a Float instance containing the value from this mutable, never null
+ */
+ public Float toFloat() {
+ return Float.valueOf(floatValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object against some other object. The result is <code>true</code> if and only if the argument is
+ * not <code>null</code> and is a <code>Float</code> object that represents a <code>float</code> that has the
+ * identical bit pattern to the bit pattern of the <code>float</code> represented by this object. For this
+ * purpose, two float values are considered to be the same if and only if the method
+ * {@link Float#floatToIntBits(float)}returns the same int value when applied to each.
+ * <p>
+ * Note that in most cases, for two instances of class <code>Float</code>,<code>f1</code> and <code>f2</code>,
+ * the value of <code>f1.equals(f2)</code> is <code>true</code> if and only if <blockquote>
+ *
+ * <pre>
+ * f1.floatValue() == f2.floatValue()
+ * </pre>
+ *
+ * </blockquote>
+ * <p>
+ * also has the value <code>true</code>. However, there are two exceptions:
+ * <ul>
+ * <li>If <code>f1</code> and <code>f2</code> both represent <code>Float.NaN</code>, then the
+ * <code>equals</code> method returns <code>true</code>, even though <code>Float.NaN==Float.NaN</code> has
+ * the value <code>false</code>.
+ * <li>If <code>f1</code> represents <code>+0.0f</code> while <code>f2</code> represents <code>-0.0f</code>,
+ * or vice versa, the <code>equal</code> test has the value <code>false</code>, even though
+ * <code>0.0f==-0.0f</code> has the value <code>true</code>.
+ * </ul>
+ * This definition allows hashtables to operate properly.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ * @see java.lang.Float#floatToIntBits(float)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof MutableFloat)
+ && (Float.floatToIntBits(((MutableFloat) obj).value) == Float.floatToIntBits(value));
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableFloat other) {
+ float anotherVal = other.value;
+ return Float.compare(value, anotherVal);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableInt.java b/src/org/apache/commons/lang3/mutable/MutableInt.java
new file mode 100644
index 0000000..eb12dad
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableInt.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>int</code> wrapper.
+ * <p>
+ * Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter.
+ *
+ * @see Integer
+ * @since 2.1
+ * @version $Id: MutableInt.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableInt extends Number implements Comparable<MutableInt>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 512176391864L;
+
+ /** The mutable value. */
+ private int value;
+
+ /**
+ * Constructs a new MutableInt with the default value of zero.
+ */
+ public MutableInt() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableInt(int value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableInt(Number value) {
+ super();
+ this.value = value.intValue();
+ }
+
+ /**
+ * Constructs a new MutableInt parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into an int
+ * @since 2.5
+ */
+ public MutableInt(String value) throws NumberFormatException {
+ super();
+ this.value = Integer.parseInt(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Integer instance.
+ *
+ * @return the value as a Integer, never null
+ */
+ public Integer getValue() {
+ return Integer.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.intValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(int operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.intValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(int operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.intValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableInt as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Integer.
+ *
+ * @return a Integer instance containing the value from this mutable, never null
+ */
+ public Integer toInteger() {
+ return Integer.valueOf(intValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is <code>true</code> if and only if the argument is
+ * not <code>null</code> and is a <code>MutableInt</code> object that contains the same <code>int</code> value
+ * as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableInt) {
+ return value == ((MutableInt) obj).intValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableInt other) {
+ int anotherVal = other.value;
+ return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableLong.java b/src/org/apache/commons/lang3/mutable/MutableLong.java
new file mode 100644
index 0000000..eea862d
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableLong.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>long</code> wrapper.
+ * <p>
+ * Note that as MutableLong does not extend Long, it is not treated by String.format as a Long parameter.
+ *
+ * @see Long
+ * @since 2.1
+ * @version $Id: MutableLong.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableLong extends Number implements Comparable<MutableLong>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 62986528375L;
+
+ /** The mutable value. */
+ private long value;
+
+ /**
+ * Constructs a new MutableLong with the default value of zero.
+ */
+ public MutableLong() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableLong with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableLong(long value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableLong with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableLong(Number value) {
+ super();
+ this.value = value.longValue();
+ }
+
+ /**
+ * Constructs a new MutableLong parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a long
+ * @since 2.5
+ */
+ public MutableLong(String value) throws NumberFormatException {
+ super();
+ this.value = Long.parseLong(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Long instance.
+ *
+ * @return the value as a Long, never null
+ */
+ public Long getValue() {
+ return Long.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.longValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(long operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.longValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(long operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.longValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableLong as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Long.
+ *
+ * @return a Long instance containing the value from this mutable, never null
+ */
+ public Long toLong() {
+ return Long.valueOf(longValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is <code>true</code> if and only if the argument
+ * is not <code>null</code> and is a <code>MutableLong</code> object that contains the same <code>long</code>
+ * value as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableLong) {
+ return value == ((MutableLong) obj).longValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return (int) (value ^ (value >>> 32));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableLong other) {
+ long anotherVal = other.value;
+ return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableObject.java b/src/org/apache/commons/lang3/mutable/MutableObject.java
new file mode 100644
index 0000000..74c4f35
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableObject.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+import java.io.Serializable;
+
+/**
+ * A mutable <code>Object</code> wrapper.
+ *
+ * @since 2.1
+ * @version $Id: MutableObject.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public class MutableObject<T> implements Mutable<T>, Serializable {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 86241875189L;
+
+ /** The mutable value. */
+ private T value;
+
+ /**
+ * Constructs a new MutableObject with the default value of <code>null</code>.
+ */
+ public MutableObject() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableObject with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableObject(T value) {
+ super();
+ this.value = value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value.
+ *
+ * @return the value, may be null
+ */
+ public T getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>
+ * Compares this object against the specified object. The result is <code>true</code> if and only if the argument
+ * is not <code>null</code> and is a <code>MutableObject</code> object that contains the same <code>T</code>
+ * value as this object.
+ * </p>
+ *
+ * @param obj the object to compare with, <code>null</code> returns <code>false</code>
+ * @return <code>true</code> if the objects are the same;
+ * <code>true</code> if the objects have equivalent <code>value</code> fields;
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() == obj.getClass()) {
+ MutableObject<?> that = (MutableObject<?>) obj;
+ return this.value.equals(that.value);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the value's hash code or <code>0</code> if the value is <code>null</code>.
+ *
+ * @return the value's hash code or <code>0</code> if the value is <code>null</code>.
+ */
+ @Override
+ public int hashCode() {
+ return value == null ? 0 : value.hashCode();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return value == null ? "null" : value.toString();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/mutable/MutableShort.java b/src/org/apache/commons/lang3/mutable/MutableShort.java
new file mode 100644
index 0000000..2dfcb00
--- /dev/null
+++ b/src/org/apache/commons/lang3/mutable/MutableShort.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable <code>short</code> wrapper.
+ * <p>
+ * Note that as MutableShort does not extend Short, it is not treated by String.format as a Short parameter.
+ *
+ * @see Short
+ * @since 2.1
+ * @version $Id: MutableShort.java 1160571 2011-08-23 07:36:08Z bayard $
+ */
+public class MutableShort extends Number implements Comparable<MutableShort>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -2135791679L;
+
+ /** The mutable value. */
+ private short value;
+
+ /**
+ * Constructs a new MutableShort with the default value of zero.
+ */
+ public MutableShort() {
+ super();
+ }
+
+ /**
+ * Constructs a new MutableShort with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableShort(short value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableShort with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableShort(Number value) {
+ super();
+ this.value = value.shortValue();
+ }
+
+ /**
+ * Constructs a new MutableShort parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a short
+ * @since 2.5
+ */
+ public MutableShort(String value) throws NumberFormatException {
+ super();
+ this.value = Short.parseShort(value);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value as a Short instance.
+ *
+ * @return the value as a Short, never null
+ */
+ public Short getValue() {
+ return Short.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(short value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value from any Number instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ public void setValue(Number value) {
+ this.value = value.shortValue();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Increments the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void increment() {
+ value++;
+ }
+
+ /**
+ * Decrements the value.
+ *
+ * @since Commons Lang 2.2
+ */
+ public void decrement() {
+ value--;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @since Commons Lang 2.2
+ */
+ public void add(short operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void add(Number operand) {
+ this.value += operand.shortValue();
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(short operand) {
+ this.value -= operand;
+ }
+
+ /**
+ * Subtracts a value from the value of this instance.
+ *
+ * @param operand the value to subtract, not null
+ * @throws NullPointerException if the object is null
+ * @since Commons Lang 2.2
+ */
+ public void subtract(Number operand) {
+ this.value -= operand.shortValue();
+ }
+
+ //-----------------------------------------------------------------------
+ // byteValue relies on Number implementation
+ /**
+ * Returns the value of this MutableShort as a short.
+ *
+ * @return the numeric value represented by this object after conversion to type short.
+ */
+ @Override
+ public short shortValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets this mutable as an instance of Short.
+ *
+ * @return a Short instance containing the value from this mutable, never null
+ */
+ public Short toShort() {
+ return Short.valueOf(shortValue());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this object to the specified object. The result is <code>true</code> if and only if the argument
+ * is not <code>null</code> and is a <code>MutableShort</code> object that contains the same <code>short</code>
+ * value as this object.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MutableShort) {
+ return value == ((MutableShort) obj).shortValue();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(MutableShort other) {
+ short anotherVal = other.value;
+ return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/time/DateFormatUtils.java b/src/org/apache/commons/lang3/time/DateFormatUtils.java
new file mode 100644
index 0000000..b3b6541
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/DateFormatUtils.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <p>Date and time formatting utilities and constants.</p>
+ *
+ * <p>Formatting is performed using the thread-safe
+ * {@link org.apache.commons.lang3.time.FastDateFormat} class.</p>
+ *
+ * @since 2.0
+ * @version $Id: DateFormatUtils.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public class DateFormatUtils {
+
+ /**
+ * The UTC time zone (often referred to as GMT).
+ * This is private as it is mutable.
+ */
+ private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT");
+ /**
+ * ISO8601 formatter for date-time without time zone.
+ * The format used is <tt>yyyy-MM-dd'T'HH:mm:ss</tt>.
+ */
+ public static final FastDateFormat ISO_DATETIME_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
+
+ /**
+ * ISO8601 formatter for date-time with time zone.
+ * The format used is <tt>yyyy-MM-dd'T'HH:mm:ssZZ</tt>.
+ */
+ public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
+
+ /**
+ * ISO8601 formatter for date without time zone.
+ * The format used is <tt>yyyy-MM-dd</tt>.
+ */
+ public static final FastDateFormat ISO_DATE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd");
+
+ /**
+ * ISO8601-like formatter for date with time zone.
+ * The format used is <tt>yyyy-MM-ddZZ</tt>.
+ * This pattern does not comply with the formal ISO8601 specification
+ * as the standard does not allow a time zone without a time.
+ */
+ public static final FastDateFormat ISO_DATE_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-ddZZ");
+
+ /**
+ * ISO8601 formatter for time without time zone.
+ * The format used is <tt>'T'HH:mm:ss</tt>.
+ */
+ public static final FastDateFormat ISO_TIME_FORMAT
+ = FastDateFormat.getInstance("'T'HH:mm:ss");
+
+ /**
+ * ISO8601 formatter for time with time zone.
+ * The format used is <tt>'T'HH:mm:ssZZ</tt>.
+ */
+ public static final FastDateFormat ISO_TIME_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("'T'HH:mm:ssZZ");
+
+ /**
+ * ISO8601-like formatter for time without time zone.
+ * The format used is <tt>HH:mm:ss</tt>.
+ * This pattern does not comply with the formal ISO8601 specification
+ * as the standard requires the 'T' prefix for times.
+ */
+ public static final FastDateFormat ISO_TIME_NO_T_FORMAT
+ = FastDateFormat.getInstance("HH:mm:ss");
+
+ /**
+ * ISO8601-like formatter for time with time zone.
+ * The format used is <tt>HH:mm:ssZZ</tt>.
+ * This pattern does not comply with the formal ISO8601 specification
+ * as the standard requires the 'T' prefix for times.
+ */
+ public static final FastDateFormat ISO_TIME_NO_T_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("HH:mm:ssZZ");
+
+ /**
+ * SMTP (and probably other) date headers.
+ * The format used is <tt>EEE, dd MMM yyyy HH:mm:ss Z</tt> in US locale.
+ */
+ public static final FastDateFormat SMTP_DATETIME_FORMAT
+ = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>DateFormatUtils instances should NOT be constructed in standard programming.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public DateFormatUtils() {
+ super();
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern using the UTC time zone.</p>
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String formatUTC(long millis, String pattern) {
+ return format(new Date(millis), pattern, UTC_TIME_ZONE, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern using the UTC time zone.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String formatUTC(Date date, String pattern) {
+ return format(date, pattern, UTC_TIME_ZONE, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern using the UTC time zone.</p>
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String formatUTC(long millis, String pattern, Locale locale) {
+ return format(new Date(millis), pattern, UTC_TIME_ZONE, locale);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern using the UTC time zone.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String formatUTC(Date date, String pattern, Locale locale) {
+ return format(date, pattern, UTC_TIME_ZONE, locale);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern.</p>
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String format(long millis, String pattern) {
+ return format(new Date(millis), pattern, null, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String format(Date date, String pattern) {
+ return format(date, pattern, null, null);
+ }
+
+ /**
+ * <p>Formats a calendar into a specific pattern.</p>
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(Calendar calendar, String pattern) {
+ return format(calendar, pattern, null, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a time zone.</p>
+ *
+ * @param millis the time expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(long millis, String pattern, TimeZone timeZone) {
+ return format(new Date(millis), pattern, timeZone, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a time zone.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(Date date, String pattern, TimeZone timeZone) {
+ return format(date, pattern, timeZone, null);
+ }
+
+ /**
+ * <p>Formats a calendar into a specific pattern in a time zone.</p>
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(Calendar calendar, String pattern, TimeZone timeZone) {
+ return format(calendar, pattern, timeZone, null);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a locale.</p>
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(long millis, String pattern, Locale locale) {
+ return format(new Date(millis), pattern, null, locale);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a locale.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(Date date, String pattern, Locale locale) {
+ return format(date, pattern, null, locale);
+ }
+
+ /**
+ * <p>Formats a calendar into a specific pattern in a locale.</p>
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(Calendar calendar, String pattern, Locale locale) {
+ return format(calendar, pattern, null, locale);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a time zone and locale.</p>
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(long millis, String pattern, TimeZone timeZone, Locale locale) {
+ return format(new Date(millis), pattern, timeZone, locale);
+ }
+
+ /**
+ * <p>Formats a date/time into a specific pattern in a time zone and locale.</p>
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted date
+ */
+ public static String format(Date date, String pattern, TimeZone timeZone, Locale locale) {
+ FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);
+ return df.format(date);
+ }
+
+ /**
+ * <p>Formats a calendar into a specific pattern in a time zone and locale.</p>
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param timeZone the time zone to use, may be <code>null</code>
+ * @param locale the locale to use, may be <code>null</code>
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(Calendar calendar, String pattern, TimeZone timeZone, Locale locale) {
+ FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);
+ return df.format(calendar);
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/time/DateUtils.java b/src/org/apache/commons/lang3/time/DateUtils.java
new file mode 100644
index 0000000..578d3a3
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/DateUtils.java
@@ -0,0 +1,1831 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>A suite of utilities surrounding the use of the
+ * {@link java.util.Calendar} and {@link java.util.Date} object.</p>
+ *
+ * <p>DateUtils contains a lot of common methods considering manipulations
+ * of Dates or Calendars. Some methods require some extra explanation.
+ * The truncate, ceiling and round methods could be considered the Math.floor(),
+ * Math.ceil() or Math.round versions for dates
+ * This way date-fields will be ignored in bottom-up order.
+ * As a complement to these methods we've introduced some fragment-methods.
+ * With these methods the Date-fields will be ignored in top-down order.
+ * Since a date without a year is not a valid date, you have to decide in what
+ * kind of date-field you want your result, for instance milliseconds or days.
+ * </p>
+ *
+ * @since 2.0
+ * @version $Id: DateUtils.java 1144992 2011-07-11 00:49:04Z ggregory $
+ */
+public class DateUtils {
+
+ /**
+ * Number of milliseconds in a standard second.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_SECOND = 1000;
+ /**
+ * Number of milliseconds in a standard minute.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+ /**
+ * Number of milliseconds in a standard hour.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
+ /**
+ * Number of milliseconds in a standard day.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
+
+ /**
+ * This is half a month, so this represents whether a date is in the top
+ * or bottom half of the month.
+ */
+ public static final int SEMI_MONTH = 1001;
+
+ private static final int[][] fields = {
+ {Calendar.MILLISECOND},
+ {Calendar.SECOND},
+ {Calendar.MINUTE},
+ {Calendar.HOUR_OF_DAY, Calendar.HOUR},
+ {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM
+ /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */
+ },
+ {Calendar.MONTH, DateUtils.SEMI_MONTH},
+ {Calendar.YEAR},
+ {Calendar.ERA}};
+
+ /**
+ * A week range, starting on Sunday.
+ */
+ public static final int RANGE_WEEK_SUNDAY = 1;
+ /**
+ * A week range, starting on Monday.
+ */
+ public static final int RANGE_WEEK_MONDAY = 2;
+ /**
+ * A week range, starting on the day focused.
+ */
+ public static final int RANGE_WEEK_RELATIVE = 3;
+ /**
+ * A week range, centered around the day focused.
+ */
+ public static final int RANGE_WEEK_CENTER = 4;
+ /**
+ * A month range, the week starting on Sunday.
+ */
+ public static final int RANGE_MONTH_SUNDAY = 5;
+ /**
+ * A month range, the week starting on Monday.
+ */
+ public static final int RANGE_MONTH_MONDAY = 6;
+
+ /**
+ * Constant marker for truncating.
+ * @since 3.0
+ */
+ private static final int MODIFY_TRUNCATE = 0;
+ /**
+ * Constant marker for rounding.
+ * @since 3.0
+ */
+ private static final int MODIFY_ROUND = 1;
+ /**
+ * Constant marker for ceiling.
+ * @since 3.0
+ */
+ private static final int MODIFY_CEILING = 2;
+
+ /**
+ * <p>{@code DateUtils} instances should NOT be constructed in
+ * standard programming. Instead, the static methods on the class should
+ * be used, such as {@code DateUtils.parseDate(str);}.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public DateUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if two date objects are on the same day ignoring time.</p>
+ *
+ * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
+ * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
+ * </p>
+ *
+ * @param date1 the first date, not altered, not null
+ * @param date2 the second date, not altered, not null
+ * @return true if they represent the same day
+ * @throws IllegalArgumentException if either date is <code>null</code>
+ * @since 2.1
+ */
+ public static boolean isSameDay(Date date1, Date date2) {
+ if (date1 == null || date2 == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar cal1 = Calendar.getInstance();
+ cal1.setTime(date1);
+ Calendar cal2 = Calendar.getInstance();
+ cal2.setTime(date2);
+ return isSameDay(cal1, cal2);
+ }
+
+ /**
+ * <p>Checks if two calendar objects are on the same day ignoring time.</p>
+ *
+ * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
+ * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
+ * </p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same day
+ * @throws IllegalArgumentException if either calendar is <code>null</code>
+ * @since 2.1
+ */
+ public static boolean isSameDay(Calendar cal1, Calendar cal2) {
+ if (cal1 == null || cal2 == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
+ cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
+ cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if two date objects represent the same instant in time.</p>
+ *
+ * <p>This method compares the long millisecond time of the two objects.</p>
+ *
+ * @param date1 the first date, not altered, not null
+ * @param date2 the second date, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws IllegalArgumentException if either date is <code>null</code>
+ * @since 2.1
+ */
+ public static boolean isSameInstant(Date date1, Date date2) {
+ if (date1 == null || date2 == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ return date1.getTime() == date2.getTime();
+ }
+
+ /**
+ * <p>Checks if two calendar objects represent the same instant in time.</p>
+ *
+ * <p>This method compares the long millisecond time of the two objects.</p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws IllegalArgumentException if either date is <code>null</code>
+ * @since 2.1
+ */
+ public static boolean isSameInstant(Calendar cal1, Calendar cal2) {
+ if (cal1 == null || cal2 == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ return cal1.getTime().getTime() == cal2.getTime().getTime();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if two calendar objects represent the same local time.</p>
+ *
+ * <p>This method compares the values of the fields of the two objects.
+ * In addition, both calendars must be the same of the same type.</p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws IllegalArgumentException if either date is <code>null</code>
+ * @since 2.1
+ */
+ public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) {
+ if (cal1 == null || cal2 == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ return (cal1.get(Calendar.MILLISECOND) == cal2.get(Calendar.MILLISECOND) &&
+ cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) &&
+ cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
+ cal1.get(Calendar.HOUR_OF_DAY) == cal2.get(Calendar.HOUR_OF_DAY) &&
+ cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
+ cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
+ cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
+ cal1.getClass() == cal2.getClass());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Parses a string representing a date by trying a variety of different parsers.</p>
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser will be lenient toward the parsed date.
+ *
+ * @param str the date to parse, not null
+ * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
+ * @return the parsed date
+ * @throws IllegalArgumentException if the date string or pattern array is null
+ * @throws ParseException if none of the date patterns were suitable (or there were none)
+ */
+ public static Date parseDate(String str, String... parsePatterns) throws ParseException {
+ return parseDateWithLeniency(str, parsePatterns, true);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Parses a string representing a date by trying a variety of different parsers.</p>
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
+ *
+ * @param str the date to parse, not null
+ * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
+ * @return the parsed date
+ * @throws IllegalArgumentException if the date string or pattern array is null
+ * @throws ParseException if none of the date patterns were suitable
+ * @since 2.5
+ */
+ public static Date parseDateStrictly(String str, String... parsePatterns) throws ParseException {
+ return parseDateWithLeniency(str, parsePatterns, false);
+ }
+
+ /**
+ * <p>Parses a string representing a date by trying a variety of different parsers.</p>
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ *
+ * @param str the date to parse, not null
+ * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
+ * @param lenient Specify whether or not date/time parsing is to be lenient.
+ * @return the parsed date
+ * @throws IllegalArgumentException if the date string or pattern array is null
+ * @throws ParseException if none of the date patterns were suitable
+ * @see java.util.Calender#isLenient()
+ */
+ private static Date parseDateWithLeniency(
+ String str, String[] parsePatterns, boolean lenient) throws ParseException {
+ if (str == null || parsePatterns == null) {
+ throw new IllegalArgumentException("Date and Patterns must not be null");
+ }
+
+ SimpleDateFormat parser = new SimpleDateFormat();
+ parser.setLenient(lenient);
+ ParsePosition pos = new ParsePosition(0);
+ for (String parsePattern : parsePatterns) {
+
+ String pattern = parsePattern;
+
+ // LANG-530 - need to make sure 'ZZ' output doesn't get passed to SimpleDateFormat
+ if (parsePattern.endsWith("ZZ")) {
+ pattern = pattern.substring(0, pattern.length() - 1);
+ }
+
+ parser.applyPattern(pattern);
+ pos.setIndex(0);
+
+ String str2 = str;
+ // LANG-530 - need to make sure 'ZZ' output doesn't hit SimpleDateFormat as it will ParseException
+ if (parsePattern.endsWith("ZZ")) {
+ str2 = str.replaceAll("([-+][0-9][0-9]):([0-9][0-9])$", "$1$2");
+ }
+
+ Date date = parser.parse(str2, pos);
+ if (date != null && pos.getIndex() == str2.length()) {
+ return date;
+ }
+ }
+ throw new ParseException("Unable to parse the date: " + str, -1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of years to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addYears(Date date, int amount) {
+ return add(date, Calendar.YEAR, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of months to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addMonths(Date date, int amount) {
+ return add(date, Calendar.MONTH, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of weeks to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addWeeks(Date date, int amount) {
+ return add(date, Calendar.WEEK_OF_YEAR, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of days to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addDays(Date date, int amount) {
+ return add(date, Calendar.DAY_OF_MONTH, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of hours to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addHours(Date date, int amount) {
+ return add(date, Calendar.HOUR_OF_DAY, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of minutes to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addMinutes(Date date, int amount) {
+ return add(date, Calendar.MINUTE, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of seconds to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addSeconds(Date date, int amount) {
+ return add(date, Calendar.SECOND, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds a number of milliseconds to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ public static Date addMilliseconds(Date date, int amount) {
+ return add(date, Calendar.MILLISECOND, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param calendarField the calendar field to add to
+ * @param amount the amount to add, may be negative
+ * @return the new {@code Date} with the amount added
+ * @throws IllegalArgumentException if the date is null
+ */
+ private static Date add(Date date, int calendarField, int amount) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(calendarField, amount);
+ return c.getTime();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the years field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setYears(Date date, int amount) {
+ return set(date, Calendar.YEAR, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the months field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setMonths(Date date, int amount) {
+ return set(date, Calendar.MONTH, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the day of month field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setDays(Date date, int amount) {
+ return set(date, Calendar.DAY_OF_MONTH, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the hours field to a date returning a new object. Hours range
+ * from 0-23.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setHours(Date date, int amount) {
+ return set(date, Calendar.HOUR_OF_DAY, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the minute field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setMinutes(Date date, int amount) {
+ return set(date, Calendar.MINUTE, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the seconds field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setSeconds(Date date, int amount) {
+ return set(date, Calendar.SECOND, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the miliseconds field to a date returning a new object.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ public static Date setMilliseconds(Date date, int amount) {
+ return set(date, Calendar.MILLISECOND, amount);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Sets the specified field to a date returning a new object.
+ * This does not use a lenient calendar.
+ * The original {@code Date} is unchanged.
+ *
+ * @param date the date, not null
+ * @param calendarField the {@code Calendar} field to set the amount to
+ * @param amount the amount to set
+ * @return a new {@code Date} set with the specified value
+ * @throws IllegalArgumentException if the date is null
+ * @since 2.4
+ */
+ private static Date set(Date date, int calendarField, int amount) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ // getInstance() returns a new object, so this method is thread safe.
+ Calendar c = Calendar.getInstance();
+ c.setLenient(false);
+ c.setTime(date);
+ c.set(calendarField, amount);
+ return c.getTime();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Convert a {@code Date} into a {@code Calendar}.
+ *
+ * @param date the date to convert to a Calendar
+ * @return the created Calendar
+ * @throws NullPointerException if null is passed in
+ * @since 3.0
+ */
+ public static Calendar toCalendar(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Round this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if this was passed with HOUR, it would return
+ * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
+ * would return 1 April 2002 0:00:00.000.</p>
+ *
+ * <p>For a date in a timezone that handles the change to daylight
+ * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
+ * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
+ * date that crosses this time would produce the following values:
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or {@code SEMI_MONTH}
+ * @return the different rounded date, not null
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Date round(Date date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar gval = Calendar.getInstance();
+ gval.setTime(date);
+ modify(gval, field, MODIFY_ROUND);
+ return gval.getTime();
+ }
+
+ /**
+ * <p>Round this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if this was passed with HOUR, it would return
+ * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
+ * would return 1 April 2002 0:00:00.000.</p>
+ *
+ * <p>For a date in a timezone that handles the change to daylight
+ * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
+ * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
+ * date that crosses this time would produce the following values:
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different rounded date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Calendar round(Calendar date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar rounded = (Calendar) date.clone();
+ modify(rounded, field, MODIFY_ROUND);
+ return rounded;
+ }
+
+ /**
+ * <p>Round this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if this was passed with HOUR, it would return
+ * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
+ * would return 1 April 2002 0:00:00.000.</p>
+ *
+ * <p>For a date in a timezone that handles the change to daylight
+ * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
+ * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
+ * date that crosses this time would produce the following values:
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, either {@code Date} or {@code Calendar}, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different rounded date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ClassCastException if the object type is not a {@code Date} or {@code Calendar}
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Date round(Object date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ if (date instanceof Date) {
+ return round((Date) date, field);
+ } else if (date instanceof Calendar) {
+ return round((Calendar) date, field).getTime();
+ } else {
+ throw new ClassCastException("Could not round " + date);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Truncate this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 13:00:00.000. If this was passed with MONTH, it would
+ * return 1 Mar 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different truncated date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Date truncate(Date date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar gval = Calendar.getInstance();
+ gval.setTime(date);
+ modify(gval, field, MODIFY_TRUNCATE);
+ return gval.getTime();
+ }
+
+ /**
+ * <p>Truncate this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 13:00:00.000. If this was passed with MONTH, it would
+ * return 1 Mar 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different truncated date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Calendar truncate(Calendar date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar truncated = (Calendar) date.clone();
+ modify(truncated, field, MODIFY_TRUNCATE);
+ return truncated;
+ }
+
+ /**
+ * <p>Truncate this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 13:00:00.000. If this was passed with MONTH, it would
+ * return 1 Mar 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, either {@code Date} or {@code Calendar}, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different truncated date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ClassCastException if the object type is not a {@code Date} or {@code Calendar}
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ public static Date truncate(Object date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ if (date instanceof Date) {
+ return truncate((Date) date, field);
+ } else if (date instanceof Calendar) {
+ return truncate((Calendar) date, field).getTime();
+ } else {
+ throw new ClassCastException("Could not truncate " + date);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Ceil this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 14:00:00.000. If this was passed with MONTH, it would
+ * return 1 Apr 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different ceil date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ArithmeticException if the year is over 280 million
+ * @since 2.5
+ */
+ public static Date ceiling(Date date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar gval = Calendar.getInstance();
+ gval.setTime(date);
+ modify(gval, field, MODIFY_CEILING);
+ return gval.getTime();
+ }
+
+ /**
+ * <p>Ceil this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 13:00:00.000. If this was passed with MONTH, it would
+ * return 1 Mar 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different ceil date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ArithmeticException if the year is over 280 million
+ * @since 2.5
+ */
+ public static Calendar ceiling(Calendar date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar ceiled = (Calendar) date.clone();
+ modify(ceiled, field, MODIFY_CEILING);
+ return ceiled;
+ }
+
+ /**
+ * <p>Ceil this date, leaving the field specified as the most
+ * significant field.</p>
+ *
+ * <p>For example, if you had the datetime of 28 Mar 2002
+ * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
+ * 2002 13:00:00.000. If this was passed with MONTH, it would
+ * return 1 Mar 2002 0:00:00.000.</p>
+ *
+ * @param date the date to work with, either {@code Date} or {@code Calendar}, not null
+ * @param field the field from {@code Calendar} or <code>SEMI_MONTH</code>
+ * @return the different ceil date, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ClassCastException if the object type is not a {@code Date} or {@code Calendar}
+ * @throws ArithmeticException if the year is over 280 million
+ * @since 2.5
+ */
+ public static Date ceiling(Object date, int field) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ if (date instanceof Date) {
+ return ceiling((Date) date, field);
+ } else if (date instanceof Calendar) {
+ return ceiling((Calendar) date, field).getTime();
+ } else {
+ throw new ClassCastException("Could not find ceiling of for type: " + date.getClass());
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Internal calculation method.</p>
+ *
+ * @param val the calendar, not null
+ * @param field the field constant
+ * @param modType type to truncate, round or ceiling
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ private static void modify(Calendar val, int field, int modType) {
+ if (val.get(Calendar.YEAR) > 280000000) {
+ throw new ArithmeticException("Calendar value too large for accurate calculations");
+ }
+
+ if (field == Calendar.MILLISECOND) {
+ return;
+ }
+
+ // ----------------- Fix for LANG-59 ---------------------- START ---------------
+ // see http://issues.apache.org/jira/browse/LANG-59
+ //
+ // Manually truncate milliseconds, seconds and minutes, rather than using
+ // Calendar methods.
+
+ Date date = val.getTime();
+ long time = date.getTime();
+ boolean done = false;
+
+ // truncate milliseconds
+ int millisecs = val.get(Calendar.MILLISECOND);
+ if (MODIFY_TRUNCATE == modType || millisecs < 500) {
+ time = time - millisecs;
+ }
+ if (field == Calendar.SECOND) {
+ done = true;
+ }
+
+ // truncate seconds
+ int seconds = val.get(Calendar.SECOND);
+ if (!done && (MODIFY_TRUNCATE == modType || seconds < 30)) {
+ time = time - (seconds * 1000L);
+ }
+ if (field == Calendar.MINUTE) {
+ done = true;
+ }
+
+ // truncate minutes
+ int minutes = val.get(Calendar.MINUTE);
+ if (!done && (MODIFY_TRUNCATE == modType || minutes < 30)) {
+ time = time - (minutes * 60000L);
+ }
+
+ // reset time
+ if (date.getTime() != time) {
+ date.setTime(time);
+ val.setTime(date);
+ }
+ // ----------------- Fix for LANG-59 ----------------------- END ----------------
+
+ boolean roundUp = false;
+ for (int[] aField : fields) {
+ for (int element : aField) {
+ if (element == field) {
+ //This is our field... we stop looping
+ if (modType == MODIFY_CEILING || (modType == MODIFY_ROUND && roundUp)) {
+ if (field == DateUtils.SEMI_MONTH) {
+ //This is a special case that's hard to generalize
+ //If the date is 1, we round up to 16, otherwise
+ // we subtract 15 days and add 1 month
+ if (val.get(Calendar.DATE) == 1) {
+ val.add(Calendar.DATE, 15);
+ } else {
+ val.add(Calendar.DATE, -15);
+ val.add(Calendar.MONTH, 1);
+ }
+// ----------------- Fix for LANG-440 ---------------------- START ---------------
+ } else if (field == Calendar.AM_PM) {
+ // This is a special case
+ // If the time is 0, we round up to 12, otherwise
+ // we subtract 12 hours and add 1 day
+ if (val.get(Calendar.HOUR_OF_DAY) == 0) {
+ val.add(Calendar.HOUR_OF_DAY, 12);
+ } else {
+ val.add(Calendar.HOUR_OF_DAY, -12);
+ val.add(Calendar.DATE, 1);
+ }
+// ----------------- Fix for LANG-440 ---------------------- END ---------------
+ } else {
+ //We need at add one to this field since the
+ // last number causes us to round up
+ val.add(aField[0], 1);
+ }
+ }
+ return;
+ }
+ }
+ //We have various fields that are not easy roundings
+ int offset = 0;
+ boolean offsetSet = false;
+ //These are special types of fields that require different rounding rules
+ switch (field) {
+ case DateUtils.SEMI_MONTH:
+ if (aField[0] == Calendar.DATE) {
+ //If we're going to drop the DATE field's value,
+ // we want to do this our own way.
+ //We need to subtrace 1 since the date has a minimum of 1
+ offset = val.get(Calendar.DATE) - 1;
+ //If we're above 15 days adjustment, that means we're in the
+ // bottom half of the month and should stay accordingly.
+ if (offset >= 15) {
+ offset -= 15;
+ }
+ //Record whether we're in the top or bottom half of that range
+ roundUp = offset > 7;
+ offsetSet = true;
+ }
+ break;
+ case Calendar.AM_PM:
+ if (aField[0] == Calendar.HOUR_OF_DAY) {
+ //If we're going to drop the HOUR field's value,
+ // we want to do this our own way.
+ offset = val.get(Calendar.HOUR_OF_DAY);
+ if (offset >= 12) {
+ offset -= 12;
+ }
+ roundUp = offset >= 6;
+ offsetSet = true;
+ }
+ break;
+ }
+ if (!offsetSet) {
+ int min = val.getActualMinimum(aField[0]);
+ int max = val.getActualMaximum(aField[0]);
+ //Calculate the offset from the minimum allowed value
+ offset = val.get(aField[0]) - min;
+ //Set roundUp if this is more than half way between the minimum and maximum
+ roundUp = offset > ((max - min) / 2);
+ }
+ //We need to remove this field
+ if (offset != 0) {
+ val.set(aField[0], val.get(aField[0]) - offset);
+ }
+ }
+ throw new IllegalArgumentException("The field " + field + " is not supported");
+
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>This constructs an <code>Iterator</code> over each day in a date
+ * range defined by a focus date and range style.</p>
+ *
+ * <p>For instance, passing Thursday, July 4, 2002 and a
+ * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
+ * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
+ * 2002, returning a Calendar instance for each intermediate day.</p>
+ *
+ * <p>This method provides an iterator that returns Calendar objects.
+ * The days are progressed using {@link Calendar#add(int, int)}.</p>
+ *
+ * @param focus the date to work with, not null
+ * @param rangeStyle the style constant to use. Must be one of
+ * {@link DateUtils#RANGE_MONTH_SUNDAY},
+ * {@link DateUtils#RANGE_MONTH_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_SUNDAY},
+ * {@link DateUtils#RANGE_WEEK_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_RELATIVE},
+ * {@link DateUtils#RANGE_WEEK_CENTER}
+ * @return the date iterator, not null, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws IllegalArgumentException if the rangeStyle is invalid
+ */
+ public static Iterator<Calendar> iterator(Date focus, int rangeStyle) {
+ if (focus == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar gval = Calendar.getInstance();
+ gval.setTime(focus);
+ return iterator(gval, rangeStyle);
+ }
+
+ /**
+ * <p>This constructs an <code>Iterator</code> over each day in a date
+ * range defined by a focus date and range style.</p>
+ *
+ * <p>For instance, passing Thursday, July 4, 2002 and a
+ * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
+ * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
+ * 2002, returning a Calendar instance for each intermediate day.</p>
+ *
+ * <p>This method provides an iterator that returns Calendar objects.
+ * The days are progressed using {@link Calendar#add(int, int)}.</p>
+ *
+ * @param focus the date to work with, not null
+ * @param rangeStyle the style constant to use. Must be one of
+ * {@link DateUtils#RANGE_MONTH_SUNDAY},
+ * {@link DateUtils#RANGE_MONTH_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_SUNDAY},
+ * {@link DateUtils#RANGE_WEEK_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_RELATIVE},
+ * {@link DateUtils#RANGE_WEEK_CENTER}
+ * @return the date iterator, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws IllegalArgumentException if the rangeStyle is invalid
+ */
+ public static Iterator<Calendar> iterator(Calendar focus, int rangeStyle) {
+ if (focus == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar start = null;
+ Calendar end = null;
+ int startCutoff = Calendar.SUNDAY;
+ int endCutoff = Calendar.SATURDAY;
+ switch (rangeStyle) {
+ case RANGE_MONTH_SUNDAY:
+ case RANGE_MONTH_MONDAY:
+ //Set start to the first of the month
+ start = truncate(focus, Calendar.MONTH);
+ //Set end to the last of the month
+ end = (Calendar) start.clone();
+ end.add(Calendar.MONTH, 1);
+ end.add(Calendar.DATE, -1);
+ //Loop start back to the previous sunday or monday
+ if (rangeStyle == RANGE_MONTH_MONDAY) {
+ startCutoff = Calendar.MONDAY;
+ endCutoff = Calendar.SUNDAY;
+ }
+ break;
+ case RANGE_WEEK_SUNDAY:
+ case RANGE_WEEK_MONDAY:
+ case RANGE_WEEK_RELATIVE:
+ case RANGE_WEEK_CENTER:
+ //Set start and end to the current date
+ start = truncate(focus, Calendar.DATE);
+ end = truncate(focus, Calendar.DATE);
+ switch (rangeStyle) {
+ case RANGE_WEEK_SUNDAY:
+ //already set by default
+ break;
+ case RANGE_WEEK_MONDAY:
+ startCutoff = Calendar.MONDAY;
+ endCutoff = Calendar.SUNDAY;
+ break;
+ case RANGE_WEEK_RELATIVE:
+ startCutoff = focus.get(Calendar.DAY_OF_WEEK);
+ endCutoff = startCutoff - 1;
+ break;
+ case RANGE_WEEK_CENTER:
+ startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
+ endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
+ break;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("The range style " + rangeStyle + " is not valid.");
+ }
+ if (startCutoff < Calendar.SUNDAY) {
+ startCutoff += 7;
+ }
+ if (startCutoff > Calendar.SATURDAY) {
+ startCutoff -= 7;
+ }
+ if (endCutoff < Calendar.SUNDAY) {
+ endCutoff += 7;
+ }
+ if (endCutoff > Calendar.SATURDAY) {
+ endCutoff -= 7;
+ }
+ while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
+ start.add(Calendar.DATE, -1);
+ }
+ while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
+ end.add(Calendar.DATE, 1);
+ }
+ return new DateIterator(start, end);
+ }
+
+ /**
+ * <p>This constructs an <code>Iterator</code> over each day in a date
+ * range defined by a focus date and range style.</p>
+ *
+ * <p>For instance, passing Thursday, July 4, 2002 and a
+ * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
+ * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
+ * 2002, returning a Calendar instance for each intermediate day.</p>
+ *
+ * @param focus the date to work with, either {@code Date} or {@code Calendar}, not null
+ * @param rangeStyle the style constant to use. Must be one of the range
+ * styles listed for the {@link #iterator(Calendar, int)} method.
+ * @return the date iterator, not null
+ * @throws IllegalArgumentException if the date is <code>null</code>
+ * @throws ClassCastException if the object type is not a {@code Date} or {@code Calendar}
+ */
+ public static Iterator<?> iterator(Object focus, int rangeStyle) {
+ if (focus == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ if (focus instanceof Date) {
+ return iterator((Date) focus, rangeStyle);
+ } else if (focus instanceof Calendar) {
+ return iterator((Calendar) focus, rangeStyle);
+ } else {
+ throw new ClassCastException("Could not iterate based on " + focus);
+ }
+ }
+
+ /**
+ * <p>Returns the number of milliseconds within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the milliseconds of any date will only return the number of milliseconds
+ * of the current second (resulting in a number between 0 and 999). This
+ * method will retrieve the number of milliseconds for any fragment.
+ * For example, if you want to calculate the number of milliseconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all milliseconds of the past hour(s), minutes(s) and second(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 (10*1000 + 538)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in milliseconds)</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param fragment the {@code Calendar} field part of date to calculate
+ * @return number of milliseconds within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInMilliseconds(Date date, int fragment) {
+ return getFragment(date, fragment, Calendar.MILLISECOND);
+ }
+
+ /**
+ * <p>Returns the number of seconds within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the seconds of any date will only return the number of seconds
+ * of the current minute (resulting in a number between 0 and 59). This
+ * method will retrieve the number of seconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s) and minutes(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to deprecated date.getSeconds())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to deprecated date.getSeconds())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110
+ * (7*3600 + 15*60 + 10)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in seconds)</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param fragment the {@code Calendar} field part of date to calculate
+ * @return number of seconds within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInSeconds(Date date, int fragment) {
+ return getFragment(date, fragment, Calendar.SECOND);
+ }
+
+ /**
+ * <p>Returns the number of minutes within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the minutes of any date will only return the number of minutes
+ * of the current hour (resulting in a number between 0 and 59). This
+ * method will retrieve the number of minutes for any fragment.
+ * For example, if you want to calculate the number of minutes past this month,
+ * your fragment is Calendar.MONTH. The result will be all minutes of the
+ * past day(s) and hour(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MINUTE field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to deprecated date.getMinutes())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to deprecated date.getMinutes())</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in minutes)</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param fragment the {@code Calendar} field part of date to calculate
+ * @return number of minutes within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInMinutes(Date date, int fragment) {
+ return getFragment(date, fragment, Calendar.MINUTE);
+ }
+
+ /**
+ * <p>Returns the number of hours within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the hours of any date will only return the number of hours
+ * of the current day (resulting in a number between 0 and 23). This
+ * method will retrieve the number of hours for any fragment.
+ * For example, if you want to calculate the number of hours past this month,
+ * your fragment is Calendar.MONTH. The result will be all hours of the
+ * past day(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a HOUR field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to deprecated date.getHours())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to deprecated date.getHours())</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in hours)</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param fragment the {@code Calendar} field part of date to calculate
+ * @return number of hours within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInHours(Date date, int fragment) {
+ return getFragment(date, fragment, Calendar.HOUR_OF_DAY);
+ }
+
+ /**
+ * <p>Returns the number of days within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the days of any date will only return the number of days
+ * of the current month (resulting in a number between 1 and 31). This
+ * method will retrieve the number of days for any fragment.
+ * For example, if you want to calculate the number of days past this year,
+ * your fragment is Calendar.YEAR. The result will be all days of the
+ * past month(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a DAY field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to deprecated date.getDay())</li>
+ * <li>February 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to deprecated date.getDay())</li>
+ * <li>January 28, 2008 with Calendar.YEAR as fragment will return 28</li>
+ * <li>February 28, 2008 with Calendar.YEAR as fragment will return 59</li>
+ * <li>January 28, 2008 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in days)</li>
+ * </ul>
+ * </p>
+ *
+ * @param date the date to work with, not null
+ * @param fragment the {@code Calendar} field part of date to calculate
+ * @return number of days within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInDays(Date date, int fragment) {
+ return getFragment(date, fragment, Calendar.DAY_OF_YEAR);
+ }
+
+ /**
+ * <p>Returns the number of milliseconds within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the milliseconds of any date will only return the number of milliseconds
+ * of the current second (resulting in a number between 0 and 999). This
+ * method will retrieve the number of milliseconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s), minutes(s) and second(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MILLISECOND field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
+ * (equivalent to calendar.get(Calendar.MILLISECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
+ * (equivalent to calendar.get(Calendar.MILLISECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538
+ * (10*1000 + 538)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in milliseconds)</li>
+ * </ul>
+ * </p>
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the {@code Calendar} field part of calendar to calculate
+ * @return number of milliseconds within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInMilliseconds(Calendar calendar, int fragment) {
+ return getFragment(calendar, fragment, Calendar.MILLISECOND);
+ }
+ /**
+ * <p>Returns the number of seconds within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the seconds of any date will only return the number of seconds
+ * of the current minute (resulting in a number between 0 and 59). This
+ * method will retrieve the number of seconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s) and minutes(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to calendar.get(Calendar.SECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to calendar.get(Calendar.SECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110
+ * (7*3600 + 15*60 + 10)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in seconds)</li>
+ * </ul>
+ * </p>
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the {@code Calendar} field part of calendar to calculate
+ * @return number of seconds within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInSeconds(Calendar calendar, int fragment) {
+ return getFragment(calendar, fragment, Calendar.SECOND);
+ }
+
+ /**
+ * <p>Returns the number of minutes within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the minutes of any date will only return the number of minutes
+ * of the current hour (resulting in a number between 0 and 59). This
+ * method will retrieve the number of minutes for any fragment.
+ * For example, if you want to calculate the number of minutes past this month,
+ * your fragment is Calendar.MONTH. The result will be all minutes of the
+ * past day(s) and hour(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MINUTE field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to calendar.get(Calendar.MINUTES))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to calendar.get(Calendar.MINUTES))</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in minutes)</li>
+ * </ul>
+ * </p>
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the {@code Calendar} field part of calendar to calculate
+ * @return number of minutes within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInMinutes(Calendar calendar, int fragment) {
+ return getFragment(calendar, fragment, Calendar.MINUTE);
+ }
+
+ /**
+ * <p>Returns the number of hours within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the hours of any date will only return the number of hours
+ * of the current day (resulting in a number between 0 and 23). This
+ * method will retrieve the number of hours for any fragment.
+ * For example, if you want to calculate the number of hours past this month,
+ * your fragment is Calendar.MONTH. The result will be all hours of the
+ * past day(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a HOUR field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to calendar.get(Calendar.HOUR_OF_DAY))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to calendar.get(Calendar.HOUR_OF_DAY))</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in hours)</li>
+ * </ul>
+ * </p>
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the {@code Calendar} field part of calendar to calculate
+ * @return number of hours within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInHours(Calendar calendar, int fragment) {
+ return getFragment(calendar, fragment, Calendar.HOUR_OF_DAY);
+ }
+
+ /**
+ * <p>Returns the number of days within the
+ * fragment. All datefields greater than the fragment will be ignored.</p>
+ *
+ * <p>Asking the days of any date will only return the number of days
+ * of the current month (resulting in a number between 1 and 31). This
+ * method will retrieve the number of days for any fragment.
+ * For example, if you want to calculate the number of days past this year,
+ * your fragment is Calendar.YEAR. The result will be all days of the
+ * past month(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a DAY field will return 0.</p>
+ *
+ * <p>
+ * <ul>
+ * <li>January 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))</li>
+ * <li>February 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))</li>
+ * <li>January 28, 2008 with Calendar.YEAR as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))</li>
+ * <li>February 28, 2008 with Calendar.YEAR as fragment will return 59
+ * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))</li>
+ * <li>January 28, 2008 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in days)</li>
+ * </ul>
+ * </p>
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the {@code Calendar} field part of calendar to calculate
+ * @return number of days within the fragment of date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ public static long getFragmentInDays(Calendar calendar, int fragment) {
+ return getFragment(calendar, fragment, Calendar.DAY_OF_YEAR);
+ }
+
+ /**
+ * Date-version for fragment-calculation in any unit
+ *
+ * @param date the date to work with, not null
+ * @param fragment the Calendar field part of date to calculate
+ * @param unit the {@code Calendar} field defining the unit
+ * @return number of units within the fragment of the date
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ private static long getFragment(Date date, int fragment, int unit) {
+ if(date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return getFragment(calendar, fragment, unit);
+ }
+
+ /**
+ * Calendar-version for fragment-calculation in any unit
+ *
+ * @param calendar the calendar to work with, not null
+ * @param fragment the Calendar field part of calendar to calculate
+ * @param unit the {@code Calendar} field defining the unit
+ * @return number of units within the fragment of the calendar
+ * @throws IllegalArgumentException if the date is <code>null</code> or
+ * fragment is not supported
+ * @since 2.4
+ */
+ private static long getFragment(Calendar calendar, int fragment, int unit) {
+ if(calendar == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ long millisPerUnit = getMillisPerUnit(unit);
+ long result = 0;
+
+ // Fragments bigger than a day require a breakdown to days
+ switch (fragment) {
+ case Calendar.YEAR:
+ result += (calendar.get(Calendar.DAY_OF_YEAR) * MILLIS_PER_DAY) / millisPerUnit;
+ break;
+ case Calendar.MONTH:
+ result += (calendar.get(Calendar.DAY_OF_MONTH) * MILLIS_PER_DAY) / millisPerUnit;
+ break;
+ }
+
+ switch (fragment) {
+ // Number of days already calculated for these cases
+ case Calendar.YEAR:
+ case Calendar.MONTH:
+
+ // The rest of the valid cases
+ case Calendar.DAY_OF_YEAR:
+ case Calendar.DATE:
+ result += (calendar.get(Calendar.HOUR_OF_DAY) * MILLIS_PER_HOUR) / millisPerUnit;
+ //$FALL-THROUGH$
+ case Calendar.HOUR_OF_DAY:
+ result += (calendar.get(Calendar.MINUTE) * MILLIS_PER_MINUTE) / millisPerUnit;
+ //$FALL-THROUGH$
+ case Calendar.MINUTE:
+ result += (calendar.get(Calendar.SECOND) * MILLIS_PER_SECOND) / millisPerUnit;
+ //$FALL-THROUGH$
+ case Calendar.SECOND:
+ result += (calendar.get(Calendar.MILLISECOND) * 1) / millisPerUnit;
+ break;
+ case Calendar.MILLISECOND: break;//never useful
+ default: throw new IllegalArgumentException("The fragment " + fragment + " is not supported");
+ }
+ return result;
+ }
+
+ /**
+ * Determines if two calendars are equal up to no more than the specified
+ * most significant field.
+ *
+ * @param cal1 the first calendar, not <code>null</code>
+ * @param cal2 the second calendar, not <code>null</code>
+ * @param field the field from {@code Calendar}
+ * @return <code>true</code> if equal; otherwise <code>false</code>
+ * @throws IllegalArgumentException if any argument is <code>null</code>
+ * @see #truncate(Calendar, int)
+ * @see #truncatedEquals(Date, Date, int)
+ * @since 3.0
+ */
+ public static boolean truncatedEquals(Calendar cal1, Calendar cal2, int field) {
+ return truncatedCompareTo(cal1, cal2, field) == 0;
+ }
+
+ /**
+ * Determines if two dates are equal up to no more than the specified
+ * most significant field.
+ *
+ * @param date1 the first date, not <code>null</code>
+ * @param date2 the second date, not <code>null</code>
+ * @param field the field from {@code Calendar}
+ * @return <code>true</code> if equal; otherwise <code>false</code>
+ * @throws IllegalArgumentException if any argument is <code>null</code>
+ * @see #truncate(Date, int)
+ * @see #truncatedEquals(Calendar, Calendar, int)
+ * @since 3.0
+ */
+ public static boolean truncatedEquals(Date date1, Date date2, int field) {
+ return truncatedCompareTo(date1, date2, field) == 0;
+ }
+
+ /**
+ * Determines how two calendars compare up to no more than the specified
+ * most significant field.
+ *
+ * @param cal1 the first calendar, not <code>null</code>
+ * @param cal2 the second calendar, not <code>null</code>
+ * @param field the field from {@code Calendar}
+ * @return a negative integer, zero, or a positive integer as the first
+ * calendar is less than, equal to, or greater than the second.
+ * @throws IllegalArgumentException if any argument is <code>null</code>
+ * @see #truncate(Calendar, int)
+ * @see #truncatedCompareTo(Date, Date, int)
+ * @since 3.0
+ */
+ public static int truncatedCompareTo(Calendar cal1, Calendar cal2, int field) {
+ Calendar truncatedCal1 = truncate(cal1, field);
+ Calendar truncatedCal2 = truncate(cal2, field);
+ return truncatedCal1.compareTo(truncatedCal2);
+ }
+
+ /**
+ * Determines how two dates compare up to no more than the specified
+ * most significant field.
+ *
+ * @param date1 the first date, not <code>null</code>
+ * @param date2 the second date, not <code>null</code>
+ * @param field the field from <code>Calendar</code>
+ * @return a negative integer, zero, or a positive integer as the first
+ * date is less than, equal to, or greater than the second.
+ * @throws IllegalArgumentException if any argument is <code>null</code>
+ * @see #truncate(Calendar, int)
+ * @see #truncatedCompareTo(Date, Date, int)
+ * @since 3.0
+ */
+ public static int truncatedCompareTo(Date date1, Date date2, int field) {
+ Date truncatedDate1 = truncate(date1, field);
+ Date truncatedDate2 = truncate(date2, field);
+ return truncatedDate1.compareTo(truncatedDate2);
+ }
+
+ /**
+ * Returns the number of milliseconds of a {@code Calendar} field, if this is a constant value.
+ * This handles millisecond, second, minute, hour and day (even though days can very in length).
+ *
+ * @param unit a {@code Calendar} field constant which is a valid unit for a fragment
+ * @return the number of milliseconds in the field
+ * @throws IllegalArgumentException if date can't be represented in milliseconds
+ * @since 2.4
+ */
+ private static long getMillisPerUnit(int unit) {
+ long result = Long.MAX_VALUE;
+ switch (unit) {
+ case Calendar.DAY_OF_YEAR:
+ case Calendar.DATE:
+ result = MILLIS_PER_DAY;
+ break;
+ case Calendar.HOUR_OF_DAY:
+ result = MILLIS_PER_HOUR;
+ break;
+ case Calendar.MINUTE:
+ result = MILLIS_PER_MINUTE;
+ break;
+ case Calendar.SECOND:
+ result = MILLIS_PER_SECOND;
+ break;
+ case Calendar.MILLISECOND:
+ result = 1;
+ break;
+ default: throw new IllegalArgumentException("The unit " + unit + " cannot be represented is milleseconds");
+ }
+ return result;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Date iterator.</p>
+ */
+ static class DateIterator implements Iterator<Calendar> {
+ private final Calendar endFinal;
+ private final Calendar spot;
+
+ /**
+ * Constructs a DateIterator that ranges from one date to another.
+ *
+ * @param startFinal start date (inclusive)
+ * @param endFinal end date (not inclusive)
+ */
+ DateIterator(Calendar startFinal, Calendar endFinal) {
+ super();
+ this.endFinal = endFinal;
+ spot = startFinal;
+ spot.add(Calendar.DATE, -1);
+ }
+
+ /**
+ * Has the iterator not reached the end date yet?
+ *
+ * @return <code>true</code> if the iterator has yet to reach the end date
+ */
+ public boolean hasNext() {
+ return spot.before(endFinal);
+ }
+
+ /**
+ * Return the next calendar in the iteration
+ *
+ * @return Object calendar for the next date
+ */
+ public Calendar next() {
+ if (spot.equals(endFinal)) {
+ throw new NoSuchElementException();
+ }
+ spot.add(Calendar.DATE, 1);
+ return (Calendar) spot.clone();
+ }
+
+ /**
+ * Always throws UnsupportedOperationException.
+ *
+ * @throws UnsupportedOperationException
+ * @see java.util.Iterator#remove()
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/time/DurationFormatUtils.java b/src/org/apache/commons/lang3/time/DurationFormatUtils.java
new file mode 100644
index 0000000..9f8d622
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -0,0 +1,662 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * <p>Duration formatting utilities and constants. The following table describes the tokens
+ * used in the pattern language for formatting. </p>
+ * <table border="1">
+ * <tr><th>character</th><th>duration element</th></tr>
+ * <tr><td>y</td><td>years</td></tr>
+ * <tr><td>M</td><td>months</td></tr>
+ * <tr><td>d</td><td>days</td></tr>
+ * <tr><td>H</td><td>hours</td></tr>
+ * <tr><td>m</td><td>minutes</td></tr>
+ * <tr><td>s</td><td>seconds</td></tr>
+ * <tr><td>S</td><td>milliseconds</td></tr>
+ * </table>
+ *
+ * @since 2.1
+ * @version $Id: DurationFormatUtils.java 1144993 2011-07-11 00:51:16Z ggregory $
+ */
+public class DurationFormatUtils {
+
+ /**
+ * <p>DurationFormatUtils instances should NOT be constructed in standard programming.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public DurationFormatUtils() {
+ super();
+ }
+
+ /**
+ * <p>Pattern used with <code>FastDateFormat</code> and <code>SimpleDateFormat</code>
+ * for the ISO8601 period format used in durations.</p>
+ *
+ * @see org.apache.commons.lang3.time.FastDateFormat
+ * @see java.text.SimpleDateFormat
+ */
+ public static final String ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'";
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Formats the time gap as a string.</p>
+ *
+ * <p>The format used is ISO8601-like:
+ * <i>H</i>:<i>m</i>:<i>s</i>.<i>S</i>.</p>
+ *
+ * @param durationMillis the duration to format
+ * @return the formatted duration, not null
+ */
+ public static String formatDurationHMS(long durationMillis) {
+ return formatDuration(durationMillis, "H:mm:ss.SSS");
+ }
+
+ /**
+ * <p>Formats the time gap as a string.</p>
+ *
+ * <p>The format used is the ISO8601 period format.</p>
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * ISO format pattern, such as P7D6TH5M4.321S.</p>
+ *
+ * @param durationMillis the duration to format
+ * @return the formatted duration, not null
+ */
+ public static String formatDurationISO(long durationMillis) {
+ return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false);
+ }
+
+ /**
+ * <p>Formats the time gap as a string, using the specified format, and padding with zeros and
+ * using the default timezone.</p>
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the duration to format
+ * @param format the way in which to format the duration, not null
+ * @return the formatted duration, not null
+ */
+ public static String formatDuration(long durationMillis, String format) {
+ return formatDuration(durationMillis, format, true);
+ }
+
+ /**
+ * <p>Formats the time gap as a string, using the specified format.
+ * Padding the left hand side of numbers with zeroes is optional and
+ * the timezone may be specified.</p>
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the duration to format
+ * @param format the way in which to format the duration, not null
+ * @param padWithZeros whether to pad the left hand side of numbers with 0's
+ * @return the formatted duration, not null
+ */
+ public static String formatDuration(long durationMillis, String format, boolean padWithZeros) {
+
+ Token[] tokens = lexx(format);
+
+ int days = 0;
+ int hours = 0;
+ int minutes = 0;
+ int seconds = 0;
+ int milliseconds = 0;
+
+ if (Token.containsTokenWithValue(tokens, d) ) {
+ days = (int) (durationMillis / DateUtils.MILLIS_PER_DAY);
+ durationMillis = durationMillis - (days * DateUtils.MILLIS_PER_DAY);
+ }
+ if (Token.containsTokenWithValue(tokens, H) ) {
+ hours = (int) (durationMillis / DateUtils.MILLIS_PER_HOUR);
+ durationMillis = durationMillis - (hours * DateUtils.MILLIS_PER_HOUR);
+ }
+ if (Token.containsTokenWithValue(tokens, m) ) {
+ minutes = (int) (durationMillis / DateUtils.MILLIS_PER_MINUTE);
+ durationMillis = durationMillis - (minutes * DateUtils.MILLIS_PER_MINUTE);
+ }
+ if (Token.containsTokenWithValue(tokens, s) ) {
+ seconds = (int) (durationMillis / DateUtils.MILLIS_PER_SECOND);
+ durationMillis = durationMillis - (seconds * DateUtils.MILLIS_PER_SECOND);
+ }
+ if (Token.containsTokenWithValue(tokens, S) ) {
+ milliseconds = (int) durationMillis;
+ }
+
+ return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros);
+ }
+
+ /**
+ * <p>Formats an elapsed time into a plurialization correct string.</p>
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the elapsed time to report in milliseconds
+ * @param suppressLeadingZeroElements suppresses leading 0 elements
+ * @param suppressTrailingZeroElements suppresses trailing 0 elements
+ * @return the formatted text in days/hours/minutes/seconds, not null
+ */
+ public static String formatDurationWords(
+ long durationMillis,
+ boolean suppressLeadingZeroElements,
+ boolean suppressTrailingZeroElements) {
+
+ // This method is generally replacable by the format method, but
+ // there are a series of tweaks and special cases that require
+ // trickery to replicate.
+ String duration = formatDuration(durationMillis, "d' days 'H' hours 'm' minutes 's' seconds'");
+ if (suppressLeadingZeroElements) {
+ // this is a temporary marker on the front. Like ^ in regexp.
+ duration = " " + duration;
+ String tmp = StringUtils.replaceOnce(duration, " 0 days", "");
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
+ duration = tmp;
+ if (tmp.length() != duration.length()) {
+ duration = StringUtils.replaceOnce(tmp, " 0 seconds", "");
+ }
+ }
+ }
+ if (duration.length() != 0) {
+ // strip the space off again
+ duration = duration.substring(1);
+ }
+ }
+ if (suppressTrailingZeroElements) {
+ String tmp = StringUtils.replaceOnce(duration, " 0 seconds", "");
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
+ if (tmp.length() != duration.length()) {
+ duration = StringUtils.replaceOnce(tmp, " 0 days", "");
+ }
+ }
+ }
+ }
+ // handle plurals
+ duration = " " + duration;
+ duration = StringUtils.replaceOnce(duration, " 1 seconds", " 1 second");
+ duration = StringUtils.replaceOnce(duration, " 1 minutes", " 1 minute");
+ duration = StringUtils.replaceOnce(duration, " 1 hours", " 1 hour");
+ duration = StringUtils.replaceOnce(duration, " 1 days", " 1 day");
+ return duration.trim();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Formats the time gap as a string.</p>
+ *
+ * <p>The format used is the ISO8601 period format.</p>
+ *
+ * @param startMillis the start of the duration to format
+ * @param endMillis the end of the duration to format
+ * @return the formatted duration, not null
+ */
+ public static String formatPeriodISO(long startMillis, long endMillis) {
+ return formatPeriod(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault());
+ }
+
+ /**
+ * <p>Formats the time gap as a string, using the specified format.
+ * Padding the left hand side of numbers with zeroes is optional.
+ *
+ * @param startMillis the start of the duration
+ * @param endMillis the end of the duration
+ * @param format the way in which to format the duration, not null
+ * @return the formatted duration, not null
+ */
+ public static String formatPeriod(long startMillis, long endMillis, String format) {
+ return formatPeriod(startMillis, endMillis, format, true, TimeZone.getDefault());
+ }
+
+ /**
+ * <p>Formats the time gap as a string, using the specified format.
+ * Padding the left hand side of numbers with zeroes is optional and
+ * the timezone may be specified. </p>
+ *
+ * <p>When calculating the difference between months/days, it chooses to
+ * calculate months first. So when working out the number of months and
+ * days between January 15th and March 10th, it choose 1 month and
+ * 23 days gained by choosing January->February = 1 month and then
+ * calculating days forwards, and not the 1 month and 26 days gained by
+ * choosing March -> February = 1 month and then calculating days
+ * backwards. </p>
+ *
+ * <p>For more control, the <a href="http://joda-time.sf.net/">Joda-Time</a>
+ * library is recommended.</p>
+ *
+ * @param startMillis the start of the duration
+ * @param endMillis the end of the duration
+ * @param format the way in which to format the duration, not null
+ * @param padWithZeros whether to pad the left hand side of numbers with 0's
+ * @param timezone the millis are defined in
+ * @return the formatted duration, not null
+ */
+ public static String formatPeriod(long startMillis, long endMillis, String format, boolean padWithZeros,
+ TimeZone timezone) {
+
+ // Used to optimise for differences under 28 days and
+ // called formatDuration(millis, format); however this did not work
+ // over leap years.
+ // TODO: Compare performance to see if anything was lost by
+ // losing this optimisation.
+
+ Token[] tokens = lexx(format);
+
+ // timezones get funky around 0, so normalizing everything to GMT
+ // stops the hours being off
+ Calendar start = Calendar.getInstance(timezone);
+ start.setTime(new Date(startMillis));
+ Calendar end = Calendar.getInstance(timezone);
+ end.setTime(new Date(endMillis));
+
+ // initial estimates
+ int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND);
+ int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
+ int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
+ int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY);
+ int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH);
+ int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
+ int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
+
+ // each initial estimate is adjusted in case it is under 0
+ while (milliseconds < 0) {
+ milliseconds += 1000;
+ seconds -= 1;
+ }
+ while (seconds < 0) {
+ seconds += 60;
+ minutes -= 1;
+ }
+ while (minutes < 0) {
+ minutes += 60;
+ hours -= 1;
+ }
+ while (hours < 0) {
+ hours += 24;
+ days -= 1;
+ }
+
+ if (Token.containsTokenWithValue(tokens, M)) {
+ while (days < 0) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ months -= 1;
+ start.add(Calendar.MONTH, 1);
+ }
+
+ while (months < 0) {
+ months += 12;
+ years -= 1;
+ }
+
+ if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
+ while (years != 0) {
+ months += 12 * years;
+ years = 0;
+ }
+ }
+ } else {
+ // there are no M's in the format string
+
+ if( !Token.containsTokenWithValue(tokens, y) ) {
+ int target = end.get(Calendar.YEAR);
+ if (months < 0) {
+ // target is end-year -1
+ target -= 1;
+ }
+
+ while ( (start.get(Calendar.YEAR) != target)) {
+ days += start.getActualMaximum(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR);
+
+ // Not sure I grok why this is needed, but the brutal tests show it is
+ if (start instanceof GregorianCalendar &&
+ start.get(Calendar.MONTH) == Calendar.FEBRUARY &&
+ start.get(Calendar.DAY_OF_MONTH) == 29) {
+ days += 1;
+ }
+
+ start.add(Calendar.YEAR, 1);
+
+ days += start.get(Calendar.DAY_OF_YEAR);
+ }
+
+ years = 0;
+ }
+
+ while( start.get(Calendar.MONTH) != end.get(Calendar.MONTH) ) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ start.add(Calendar.MONTH, 1);
+ }
+
+ months = 0;
+
+ while (days < 0) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ months -= 1;
+ start.add(Calendar.MONTH, 1);
+ }
+
+ }
+
+ // The rest of this code adds in values that
+ // aren't requested. This allows the user to ask for the
+ // number of months and get the real count and not just 0->11.
+
+ if (!Token.containsTokenWithValue(tokens, d)) {
+ hours += 24 * days;
+ days = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, H)) {
+ minutes += 60 * hours;
+ hours = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, m)) {
+ seconds += 60 * minutes;
+ minutes = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, s)) {
+ milliseconds += 1000 * seconds;
+ seconds = 0;
+ }
+
+ return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>The internal method to do the formatting.</p>
+ *
+ * @param tokens the tokens
+ * @param years the number of years
+ * @param months the number of months
+ * @param days the number of days
+ * @param hours the number of hours
+ * @param minutes the number of minutes
+ * @param seconds the number of seconds
+ * @param milliseconds the number of millis
+ * @param padWithZeros whether to pad
+ * @return the formatted string
+ */
+ static String format(Token[] tokens, int years, int months, int days, int hours, int minutes, int seconds,
+ int milliseconds, boolean padWithZeros) {
+ StringBuffer buffer = new StringBuffer();
+ boolean lastOutputSeconds = false;
+ int sz = tokens.length;
+ for (int i = 0; i < sz; i++) {
+ Token token = tokens[i];
+ Object value = token.getValue();
+ int count = token.getCount();
+ if (value instanceof StringBuffer) {
+ buffer.append(value.toString());
+ } else {
+ if (value == y) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(years), count, '0') : Integer
+ .toString(years));
+ lastOutputSeconds = false;
+ } else if (value == M) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(months), count, '0') : Integer
+ .toString(months));
+ lastOutputSeconds = false;
+ } else if (value == d) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(days), count, '0') : Integer
+ .toString(days));
+ lastOutputSeconds = false;
+ } else if (value == H) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(hours), count, '0') : Integer
+ .toString(hours));
+ lastOutputSeconds = false;
+ } else if (value == m) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(minutes), count, '0') : Integer
+ .toString(minutes));
+ lastOutputSeconds = false;
+ } else if (value == s) {
+ buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(seconds), count, '0') : Integer
+ .toString(seconds));
+ lastOutputSeconds = true;
+ } else if (value == S) {
+ if (lastOutputSeconds) {
+ milliseconds += 1000;
+ String str = padWithZeros
+ ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
+ : Integer.toString(milliseconds);
+ buffer.append(str.substring(1));
+ } else {
+ buffer.append(padWithZeros
+ ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
+ : Integer.toString(milliseconds));
+ }
+ lastOutputSeconds = false;
+ }
+ }
+ }
+ return buffer.toString();
+ }
+
+ static final Object y = "y";
+ static final Object M = "M";
+ static final Object d = "d";
+ static final Object H = "H";
+ static final Object m = "m";
+ static final Object s = "s";
+ static final Object S = "S";
+
+ /**
+ * Parses a classic date format string into Tokens
+ *
+ * @param format the format to parse, not null
+ * @return array of Token[]
+ */
+ static Token[] lexx(String format) {
+ char[] array = format.toCharArray();
+ ArrayList<Token> list = new ArrayList<Token>(array.length);
+
+ boolean inLiteral = false;
+ StringBuffer buffer = null;
+ Token previous = null;
+ int sz = array.length;
+ for(int i=0; i<sz; i++) {
+ char ch = array[i];
+ if(inLiteral && ch != '\'') {
+ buffer.append(ch); // buffer can't be null if inLiteral is true
+ continue;
+ }
+ Object value = null;
+ switch(ch) {
+ // TODO: Need to handle escaping of '
+ case '\'' :
+ if(inLiteral) {
+ buffer = null;
+ inLiteral = false;
+ } else {
+ buffer = new StringBuffer();
+ list.add(new Token(buffer));
+ inLiteral = true;
+ }
+ break;
+ case 'y' : value = y; break;
+ case 'M' : value = M; break;
+ case 'd' : value = d; break;
+ case 'H' : value = H; break;
+ case 'm' : value = m; break;
+ case 's' : value = s; break;
+ case 'S' : value = S; break;
+ default :
+ if(buffer == null) {
+ buffer = new StringBuffer();
+ list.add(new Token(buffer));
+ }
+ buffer.append(ch);
+ }
+
+ if(value != null) {
+ if(previous != null && previous.getValue() == value) {
+ previous.increment();
+ } else {
+ Token token = new Token(value);
+ list.add(token);
+ previous = token;
+ }
+ buffer = null;
+ }
+ }
+ return list.toArray( new Token[list.size()] );
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Element that is parsed from the format pattern.
+ */
+ static class Token {
+
+ /**
+ * Helper method to determine if a set of tokens contain a value
+ *
+ * @param tokens set to look in
+ * @param value to look for
+ * @return boolean <code>true</code> if contained
+ */
+ static boolean containsTokenWithValue(Token[] tokens, Object value) {
+ int sz = tokens.length;
+ for (int i = 0; i < sz; i++) {
+ if (tokens[i].getValue() == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final Object value;
+ private int count;
+
+ /**
+ * Wraps a token around a value. A value would be something like a 'Y'.
+ *
+ * @param value to wrap
+ */
+ Token(Object value) {
+ this.value = value;
+ this.count = 1;
+ }
+
+ /**
+ * Wraps a token around a repeated number of a value, for example it would
+ * store 'yyyy' as a value for y and a count of 4.
+ *
+ * @param value to wrap
+ * @param count to wrap
+ */
+ Token(Object value, int count) {
+ this.value = value;
+ this.count = count;
+ }
+
+ /**
+ * Adds another one of the value
+ */
+ void increment() {
+ count++;
+ }
+
+ /**
+ * Gets the current number of values represented
+ *
+ * @return int number of values represented
+ */
+ int getCount() {
+ return count;
+ }
+
+ /**
+ * Gets the particular value this token represents.
+ *
+ * @return Object value
+ */
+ Object getValue() {
+ return value;
+ }
+
+ /**
+ * Supports equality of this Token to another Token.
+ *
+ * @param obj2 Object to consider equality of
+ * @return boolean <code>true</code> if equal
+ */
+ @Override
+ public boolean equals(Object obj2) {
+ if (obj2 instanceof Token) {
+ Token tok2 = (Token) obj2;
+ if (this.value.getClass() != tok2.value.getClass()) {
+ return false;
+ }
+ if (this.count != tok2.count) {
+ return false;
+ }
+ if (this.value instanceof StringBuffer) {
+ return this.value.toString().equals(tok2.value.toString());
+ } else if (this.value instanceof Number) {
+ return this.value.equals(tok2.value);
+ } else {
+ return this.value == tok2.value;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash code for the token equal to the
+ * hash code for the token's value. Thus 'TT' and 'TTTT'
+ * will have the same hash code.
+ *
+ * @return The hash code for the token
+ */
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+
+ /**
+ * Represents this token as a String.
+ *
+ * @return String representation of the token
+ */
+ @Override
+ public String toString() {
+ return StringUtils.repeat(this.value.toString(), this.count);
+ }
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/time/FastDateFormat.java b/src/org/apache/commons/lang3/time/FastDateFormat.java
new file mode 100644
index 0000000..3a60527
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/FastDateFormat.java
@@ -0,0 +1,1519 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * <p>FastDateFormat is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.</p>
+ *
+ * <p>This class can be used as a direct replacement to
+ * {@code SimpleDateFormat} in most formatting situations.
+ * This class is especially useful in multi-threaded server environments.
+ * {@code SimpleDateFormat} is not thread-safe in any JDK version,
+ * nor will it be as Sun have closed the bug/RFE.
+ * </p>
+ *
+ * <p>Only formatting is supported, but all patterns are compatible with
+ * SimpleDateFormat (except time zones and some year patterns - see below).</p>
+ *
+ * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
+ * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
+ * This pattern letter can be used here (on all JDK versions).</p>
+ *
+ * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
+ * ISO8601 full format time zones (eg. {@code +08:00} or {@code -11:00}).
+ * This introduces a minor incompatibility with Java 1.4, but at a gain of
+ * useful functionality.</p>
+ *
+ * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
+ * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
+ * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
+ * 'YYY' will be formatted as '2003', while it was '03' in former Java
+ * versions. FastDateFormat implements the behavior of Java 7.</p>
+ *
+ * @since 2.0
+ * @version $Id: FastDateFormat.java 1146138 2011-07-13 17:01:37Z joehni $
+ */
+public class FastDateFormat extends Format {
+ // A lot of the speed in this class comes from caching, but some comes
+ // from the special int to StringBuffer conversion.
+ //
+ // The following produces a padded 2 digit number:
+ // buffer.append((char)(value / 10 + '0'));
+ // buffer.append((char)(value % 10 + '0'));
+ //
+ // Note that the fastest append to StringBuffer is a single char (used here).
+ // Note that Integer.toString() is not called, the conversion is simply
+ // taking the value and adding (mathematically) the ASCII value for '0'.
+ // So, don't change this code! It works and is very fast.
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * FULL locale dependent date or time style.
+ */
+ public static final int FULL = DateFormat.FULL;
+ /**
+ * LONG locale dependent date or time style.
+ */
+ public static final int LONG = DateFormat.LONG;
+ /**
+ * MEDIUM locale dependent date or time style.
+ */
+ public static final int MEDIUM = DateFormat.MEDIUM;
+ /**
+ * SHORT locale dependent date or time style.
+ */
+ public static final int SHORT = DateFormat.SHORT;
+
+ private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
+ @Override
+ protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) {
+ return new FastDateFormat(pattern, timeZone, locale);
+ }
+ };
+
+ private static ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
+ new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
+
+ /**
+ * The pattern.
+ */
+ private final String mPattern;
+ /**
+ * The time zone.
+ */
+ private final TimeZone mTimeZone;
+ /**
+ * The locale.
+ */
+ private final Locale mLocale;
+ /**
+ * The parsed rules.
+ */
+ private transient Rule[] mRules;
+ /**
+ * The estimated maximum length.
+ */
+ private transient int mMaxLengthEstimate;
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a formatter instance using the default pattern in the
+ * default locale.</p>
+ *
+ * @return a date/time formatter
+ */
+ public static FastDateFormat getInstance() {
+ return cache.getDateTimeInstance(SHORT, SHORT, null, null);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern in the
+ * default locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(String pattern) {
+ return cache.getInstance(pattern, null, null);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern and
+ * time zone.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
+ return cache.getInstance(pattern, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern and
+ * locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param locale optional locale, overrides system locale
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(String pattern, Locale locale) {
+ return cache.getInstance(pattern, null, locale);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern, time zone
+ * and locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ * or {@code null}
+ */
+ public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
+ return cache.getInstance(pattern, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a date formatter instance using the specified style in the
+ * default time zone and locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(int style) {
+ return cache.getDateTimeInstance(style, null, null, null);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(int style, Locale locale) {
+ return cache.getDateTimeInstance(style, null, null, locale);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
+ return cache.getDateTimeInstance(style, null, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style, time
+ * zone and locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ */
+ public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
+ return cache.getDateTimeInstance(style, null, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a time formatter instance using the specified style in the
+ * default time zone and locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(int style) {
+ return cache.getDateTimeInstance(null, style, null, null);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(int style, Locale locale) {
+ return cache.getDateTimeInstance(null, style, null, locale);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
+ return cache.getDateTimeInstance(null, style, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style, time
+ * zone and locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ */
+ public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
+ return cache.getDateTimeInstance(null, style, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a date/time formatter instance using the specified style
+ * in the default time zone and locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
+ }
+
+ /**
+ * <p>Gets a date/time formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
+ }
+
+ /**
+ * <p>Gets a date/time formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) {
+ return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
+ }
+ /**
+ * <p>Gets a date/time formatter instance using the specified style,
+ * time zone and locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ */
+ public static FastDateFormat getDateTimeInstance(
+ int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the time zone display name, using a cache for performance.</p>
+ *
+ * @param tz the zone to query
+ * @param daylight true if daylight savings
+ * @param style the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
+ * @param locale the locale to use
+ * @return the textual name of the time zone
+ */
+ static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
+ TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
+ String value = cTimeZoneDisplayCache.get(key);
+ if (value == null) {
+ // This is a very slow call, so cache the results.
+ value = tz.getDisplayName(daylight, style, locale);
+ String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
+ if (prior != null) {
+ value= prior;
+ }
+ }
+ return value;
+ }
+
+ // Constructor
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Constructs a new FastDateFormat.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
+ mPattern = pattern;
+ mTimeZone = timeZone;
+ mLocale = locale;
+
+ init();
+ }
+
+ /**
+ * <p>Initializes the instance for first use.</p>
+ */
+ private void init() {
+ List<Rule> rulesList = parsePattern();
+ mRules = rulesList.toArray(new Rule[rulesList.size()]);
+
+ int len = 0;
+ for (int i=mRules.length; --i >= 0; ) {
+ len += mRules[i].estimateLength();
+ }
+
+ mMaxLengthEstimate = len;
+ }
+
+ // Parse the pattern
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Returns a list of Rules given a pattern.</p>
+ *
+ * @return a {@code List} of Rule objects
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ protected List<Rule> parsePattern() {
+ DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
+ List<Rule> rules = new ArrayList<Rule>();
+
+ String[] ERAs = symbols.getEras();
+ String[] months = symbols.getMonths();
+ String[] shortMonths = symbols.getShortMonths();
+ String[] weekdays = symbols.getWeekdays();
+ String[] shortWeekdays = symbols.getShortWeekdays();
+ String[] AmPmStrings = symbols.getAmPmStrings();
+
+ int length = mPattern.length();
+ int[] indexRef = new int[1];
+
+ for (int i = 0; i < length; i++) {
+ indexRef[0] = i;
+ String token = parseToken(mPattern, indexRef);
+ i = indexRef[0];
+
+ int tokenLen = token.length();
+ if (tokenLen == 0) {
+ break;
+ }
+
+ Rule rule;
+ char c = token.charAt(0);
+
+ switch (c) {
+ case 'G': // era designator (text)
+ rule = new TextField(Calendar.ERA, ERAs);
+ break;
+ case 'y': // year (number)
+ if (tokenLen == 2) {
+ rule = TwoDigitYearField.INSTANCE;
+ } else {
+ rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
+ }
+ break;
+ case 'M': // month in year (text and number)
+ if (tokenLen >= 4) {
+ rule = new TextField(Calendar.MONTH, months);
+ } else if (tokenLen == 3) {
+ rule = new TextField(Calendar.MONTH, shortMonths);
+ } else if (tokenLen == 2) {
+ rule = TwoDigitMonthField.INSTANCE;
+ } else {
+ rule = UnpaddedMonthField.INSTANCE;
+ }
+ break;
+ case 'd': // day in month (number)
+ rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
+ break;
+ case 'h': // hour in am/pm (number, 1..12)
+ rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
+ break;
+ case 'H': // hour in day (number, 0..23)
+ rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
+ break;
+ case 'm': // minute in hour (number)
+ rule = selectNumberRule(Calendar.MINUTE, tokenLen);
+ break;
+ case 's': // second in minute (number)
+ rule = selectNumberRule(Calendar.SECOND, tokenLen);
+ break;
+ case 'S': // millisecond (number)
+ rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
+ break;
+ case 'E': // day in week (text)
+ rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
+ break;
+ case 'D': // day in year (number)
+ rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
+ break;
+ case 'F': // day of week in month (number)
+ rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
+ break;
+ case 'w': // week in year (number)
+ rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
+ break;
+ case 'W': // week in month (number)
+ rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
+ break;
+ case 'a': // am/pm marker (text)
+ rule = new TextField(Calendar.AM_PM, AmPmStrings);
+ break;
+ case 'k': // hour in day (1..24)
+ rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
+ break;
+ case 'K': // hour in am/pm (0..11)
+ rule = selectNumberRule(Calendar.HOUR, tokenLen);
+ break;
+ case 'z': // time zone (text)
+ if (tokenLen >= 4) {
+ rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
+ } else {
+ rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
+ }
+ break;
+ case 'Z': // time zone (value)
+ if (tokenLen == 1) {
+ rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
+ } else {
+ rule = TimeZoneNumberRule.INSTANCE_COLON;
+ }
+ break;
+ case '\'': // literal text
+ String sub = token.substring(1);
+ if (sub.length() == 1) {
+ rule = new CharacterLiteral(sub.charAt(0));
+ } else {
+ rule = new StringLiteral(sub);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal pattern component: " + token);
+ }
+
+ rules.add(rule);
+ }
+
+ return rules;
+ }
+
+ /**
+ * <p>Performs the parsing of tokens.</p>
+ *
+ * @param pattern the pattern
+ * @param indexRef index references
+ * @return parsed token
+ */
+ protected String parseToken(String pattern, int[] indexRef) {
+ StringBuilder buf = new StringBuilder();
+
+ int i = indexRef[0];
+ int length = pattern.length();
+
+ char c = pattern.charAt(i);
+ if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
+ // Scan a run of the same character, which indicates a time
+ // pattern.
+ buf.append(c);
+
+ while (i + 1 < length) {
+ char peek = pattern.charAt(i + 1);
+ if (peek == c) {
+ buf.append(c);
+ i++;
+ } else {
+ break;
+ }
+ }
+ } else {
+ // This will identify token as text.
+ buf.append('\'');
+
+ boolean inLiteral = false;
+
+ for (; i < length; i++) {
+ c = pattern.charAt(i);
+
+ if (c == '\'') {
+ if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
+ // '' is treated as escaped '
+ i++;
+ buf.append(c);
+ } else {
+ inLiteral = !inLiteral;
+ }
+ } else if (!inLiteral &&
+ (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
+ i--;
+ break;
+ } else {
+ buf.append(c);
+ }
+ }
+ }
+
+ indexRef[0] = i;
+ return buf.toString();
+ }
+
+ /**
+ * <p>Gets an appropriate rule for the padding required.</p>
+ *
+ * @param field the field to get a rule for
+ * @param padding the padding required
+ * @return a new rule with the correct padding
+ */
+ protected NumberRule selectNumberRule(int field, int padding) {
+ switch (padding) {
+ case 1:
+ return new UnpaddedNumberField(field);
+ case 2:
+ return new TwoDigitNumberField(field);
+ default:
+ return new PaddedNumberField(field, padding);
+ }
+ }
+
+ // Format methods
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Formats a {@code Date}, {@code Calendar} or
+ * {@code Long} (milliseconds) object.</p>
+ *
+ * @param obj the object to format
+ * @param toAppendTo the buffer to append to
+ * @param pos the position - ignored
+ * @return the buffer passed in
+ */
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ if (obj instanceof Date) {
+ return format((Date) obj, toAppendTo);
+ } else if (obj instanceof Calendar) {
+ return format((Calendar) obj, toAppendTo);
+ } else if (obj instanceof Long) {
+ return format(((Long) obj).longValue(), toAppendTo);
+ } else {
+ throw new IllegalArgumentException("Unknown class: " +
+ (obj == null ? "<null>" : obj.getClass().getName()));
+ }
+ }
+
+ /**
+ * <p>Formats a millisecond {@code long} value.</p>
+ *
+ * @param millis the millisecond value to format
+ * @return the formatted string
+ * @since 2.1
+ */
+ public String format(long millis) {
+ return format(new Date(millis));
+ }
+
+ /**
+ * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @return the formatted string
+ */
+ public String format(Date date) {
+ Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar
+ c.setTime(date);
+ return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
+ }
+
+ /**
+ * <p>Formats a {@code Calendar} object.</p>
+ *
+ * @param calendar the calendar to format
+ * @return the formatted string
+ */
+ public String format(Calendar calendar) {
+ return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
+ }
+
+ /**
+ * <p>Formats a milliseond {@code long} value into the
+ * supplied {@code StringBuffer}.</p>
+ *
+ * @param millis the millisecond value to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ * @since 2.1
+ */
+ public StringBuffer format(long millis, StringBuffer buf) {
+ return format(new Date(millis), buf);
+ }
+
+ /**
+ * <p>Formats a {@code Date} object into the
+ * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ public StringBuffer format(Date date, StringBuffer buf) {
+ Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar
+ c.setTime(date);
+ return applyRules(c, buf);
+ }
+
+ /**
+ * <p>Formats a {@code Calendar} object into the
+ * supplied {@code StringBuffer}.</p>
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ public StringBuffer format(Calendar calendar, StringBuffer buf) {
+ return applyRules(calendar, buf);
+ }
+
+ /**
+ * <p>Performs the formatting by applying the rules to the
+ * specified calendar.</p>
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
+ for (Rule rule : mRules) {
+ rule.appendTo(buf, calendar);
+ }
+ return buf;
+ }
+
+ // Parsing
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Parsing is not supported.</p>
+ *
+ * @param source the string to parse
+ * @param pos the parsing position
+ * @return {@code null} as not supported
+ */
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ pos.setIndex(0);
+ pos.setErrorIndex(0);
+ return null;
+ }
+
+ // Accessors
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the pattern used by this formatter.</p>
+ *
+ * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+ */
+ public String getPattern() {
+ return mPattern;
+ }
+
+ /**
+ * <p>Gets the time zone used by this formatter.</p>
+ *
+ * <p>This zone is always used for {@code Date} formatting. </p>
+ *
+ * @return the time zone
+ */
+ public TimeZone getTimeZone() {
+ return mTimeZone;
+ }
+
+ /**
+ * <p>Gets the locale used by this formatter.</p>
+ *
+ * @return the locale
+ */
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * <p>Gets an estimate for the maximum string length that the
+ * formatter will produce.</p>
+ *
+ * <p>The actual formatted length will almost always be less than or
+ * equal to this amount.</p>
+ *
+ * @return the maximum formatted length
+ */
+ public int getMaxLengthEstimate() {
+ return mMaxLengthEstimate;
+ }
+
+ // Basics
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares two objects for equality.</p>
+ *
+ * @param obj the object to compare to
+ * @return {@code true} if equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof FastDateFormat == false) {
+ return false;
+ }
+ FastDateFormat other = (FastDateFormat) obj;
+ return mPattern.equals(other.mPattern)
+ && mTimeZone.equals(other.mTimeZone)
+ && mLocale.equals(other.mLocale);
+ }
+
+ /**
+ * <p>Returns a hashcode compatible with equals.</p>
+ *
+ * @return a hashcode compatible with equals
+ */
+ @Override
+ public int hashCode() {
+ return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
+ }
+
+ /**
+ * <p>Gets a debugging string version of this formatter.</p>
+ *
+ * @return a debugging string
+ */
+ @Override
+ public String toString() {
+ return "FastDateFormat[" + mPattern + "]";
+ }
+
+ // Serializing
+ //-----------------------------------------------------------------------
+ /**
+ * Create the object after serialization. This implementation reinitializes the
+ * transient properties.
+ *
+ * @param in ObjectInputStream from which the object is being deserialized.
+ * @throws IOException if there is an IO issue.
+ * @throws ClassNotFoundException if a class cannot be found.
+ */
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ init();
+ }
+
+ // Rules
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Inner class defining a rule.</p>
+ */
+ private interface Rule {
+ /**
+ * Returns the estimated lentgh of the result.
+ *
+ * @return the estimated length
+ */
+ int estimateLength();
+
+ /**
+ * Appends the value of the specified calendar to the output buffer based on the rule implementation.
+ *
+ * @param buffer the output buffer
+ * @param calendar calendar to be appended
+ */
+ void appendTo(StringBuffer buffer, Calendar calendar);
+ }
+
+ /**
+ * <p>Inner class defining a numeric rule.</p>
+ */
+ private interface NumberRule extends Rule {
+ /**
+ * Appends the specified value to the output buffer based on the rule implementation.
+ *
+ * @param buffer the output buffer
+ * @param value the value to be appended
+ */
+ void appendTo(StringBuffer buffer, int value);
+ }
+
+ /**
+ * <p>Inner class to output a constant single character.</p>
+ */
+ private static class CharacterLiteral implements Rule {
+ private final char mValue;
+
+ /**
+ * Constructs a new instance of {@code CharacterLiteral}
+ * to hold the specified value.
+ *
+ * @param value the character literal
+ */
+ CharacterLiteral(char value) {
+ mValue = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 1;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ buffer.append(mValue);
+ }
+ }
+
+ /**
+ * <p>Inner class to output a constant string.</p>
+ */
+ private static class StringLiteral implements Rule {
+ private final String mValue;
+
+ /**
+ * Constructs a new instance of {@code StringLiteral}
+ * to hold the specified value.
+ *
+ * @param value the string literal
+ */
+ StringLiteral(String value) {
+ mValue = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return mValue.length();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ buffer.append(mValue);
+ }
+ }
+
+ /**
+ * <p>Inner class to output one of a set of values.</p>
+ */
+ private static class TextField implements Rule {
+ private final int mField;
+ private final String[] mValues;
+
+ /**
+ * Constructs an instance of {@code TextField}
+ * with the specified field and values.
+ *
+ * @param field the field
+ * @param values the field values
+ */
+ TextField(int field, String[] values) {
+ mField = field;
+ mValues = values;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ int max = 0;
+ for (int i=mValues.length; --i >= 0; ) {
+ int len = mValues[i].length();
+ if (len > max) {
+ max = len;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ buffer.append(mValues[calendar.get(mField)]);
+ }
+ }
+
+ /**
+ * <p>Inner class to output an unpadded number.</p>
+ */
+ private static class UnpaddedNumberField implements NumberRule {
+ private final int mField;
+
+ /**
+ * Constructs an instance of {@code UnpadedNumberField} with the specified field.
+ *
+ * @param field the field
+ */
+ UnpaddedNumberField(int field) {
+ mField = field;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 4;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(mField));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ if (value < 10) {
+ buffer.append((char)(value + '0'));
+ } else if (value < 100) {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ } else {
+ buffer.append(Integer.toString(value));
+ }
+ }
+ }
+
+ /**
+ * <p>Inner class to output an unpadded month.</p>
+ */
+ private static class UnpaddedMonthField implements NumberRule {
+ static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
+
+ /**
+ * Constructs an instance of {@code UnpaddedMonthField}.
+ *
+ */
+ UnpaddedMonthField() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ if (value < 10) {
+ buffer.append((char)(value + '0'));
+ } else {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ }
+ }
+ }
+
+ /**
+ * <p>Inner class to output a padded number.</p>
+ */
+ private static class PaddedNumberField implements NumberRule {
+ private final int mField;
+ private final int mSize;
+
+ /**
+ * Constructs an instance of {@code PaddedNumberField}.
+ *
+ * @param field the field
+ * @param size size of the output field
+ */
+ PaddedNumberField(int field, int size) {
+ if (size < 3) {
+ // Should use UnpaddedNumberField or TwoDigitNumberField.
+ throw new IllegalArgumentException();
+ }
+ mField = field;
+ mSize = size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 4;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(mField));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ if (value < 100) {
+ for (int i = mSize; --i >= 2; ) {
+ buffer.append('0');
+ }
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ } else {
+ int digits;
+ if (value < 1000) {
+ digits = 3;
+ } else {
+ Validate.isTrue(value > -1, "Negative values should not be possible", value);
+ digits = Integer.toString(value).length();
+ }
+ for (int i = mSize; --i >= digits; ) {
+ buffer.append('0');
+ }
+ buffer.append(Integer.toString(value));
+ }
+ }
+ }
+
+ /**
+ * <p>Inner class to output a two digit number.</p>
+ */
+ private static class TwoDigitNumberField implements NumberRule {
+ private final int mField;
+
+ /**
+ * Constructs an instance of {@code TwoDigitNumberField} with the specified field.
+ *
+ * @param field the field
+ */
+ TwoDigitNumberField(int field) {
+ mField = field;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(mField));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ if (value < 100) {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ } else {
+ buffer.append(Integer.toString(value));
+ }
+ }
+ }
+
+ /**
+ * <p>Inner class to output a two digit year.</p>
+ */
+ private static class TwoDigitYearField implements NumberRule {
+ static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
+
+ /**
+ * Constructs an instance of {@code TwoDigitYearField}.
+ */
+ TwoDigitYearField() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ }
+ }
+
+ /**
+ * <p>Inner class to output a two digit month.</p>
+ */
+ private static class TwoDigitMonthField implements NumberRule {
+ static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
+
+ /**
+ * Constructs an instance of {@code TwoDigitMonthField}.
+ */
+ TwoDigitMonthField() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void appendTo(StringBuffer buffer, int value) {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ }
+ }
+
+ /**
+ * <p>Inner class to output the twelve hour field.</p>
+ */
+ private static class TwelveHourField implements NumberRule {
+ private final NumberRule mRule;
+
+ /**
+ * Constructs an instance of {@code TwelveHourField} with the specified
+ * {@code NumberRule}.
+ *
+ * @param rule the rule
+ */
+ TwelveHourField(NumberRule rule) {
+ mRule = rule;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return mRule.estimateLength();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ int value = calendar.get(Calendar.HOUR);
+ if (value == 0) {
+ value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
+ }
+ mRule.appendTo(buffer, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, int value) {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+ /**
+ * <p>Inner class to output the twenty four hour field.</p>
+ */
+ private static class TwentyFourHourField implements NumberRule {
+ private final NumberRule mRule;
+
+ /**
+ * Constructs an instance of {@code TwentyFourHourField} with the specified
+ * {@code NumberRule}.
+ *
+ * @param rule the rule
+ */
+ TwentyFourHourField(NumberRule rule) {
+ mRule = rule;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return mRule.estimateLength();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ int value = calendar.get(Calendar.HOUR_OF_DAY);
+ if (value == 0) {
+ value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
+ }
+ mRule.appendTo(buffer, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, int value) {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+ /**
+ * <p>Inner class to output a time zone name.</p>
+ */
+ private static class TimeZoneNameRule implements Rule {
+ private final TimeZone mTimeZone;
+ private final String mStandard;
+ private final String mDaylight;
+
+ /**
+ * Constructs an instance of {@code TimeZoneNameRule} with the specified properties.
+ *
+ * @param timeZone the time zone
+ * @param locale the locale
+ * @param style the style
+ */
+ TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) {
+ mTimeZone = timeZone;
+
+ mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
+ mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return Math.max(mStandard.length(), mDaylight.length());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
+ buffer.append(mDaylight);
+ } else {
+ buffer.append(mStandard);
+ }
+ }
+ }
+
+ /**
+ * <p>Inner class to output a time zone as a number {@code +/-HHMM}
+ * or {@code +/-HH:MM}.</p>
+ */
+ private static class TimeZoneNumberRule implements Rule {
+ static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
+ static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
+
+ final boolean mColon;
+
+ /**
+ * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties.
+ *
+ * @param colon add colon between HH and MM in the output if {@code true}
+ */
+ TimeZoneNumberRule(boolean colon) {
+ mColon = colon;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int estimateLength() {
+ return 5;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void appendTo(StringBuffer buffer, Calendar calendar) {
+ int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+
+ if (offset < 0) {
+ buffer.append('-');
+ offset = -offset;
+ } else {
+ buffer.append('+');
+ }
+
+ int hours = offset / (60 * 60 * 1000);
+ buffer.append((char)(hours / 10 + '0'));
+ buffer.append((char)(hours % 10 + '0'));
+
+ if (mColon) {
+ buffer.append(':');
+ }
+
+ int minutes = offset / (60 * 1000) - 60 * hours;
+ buffer.append((char)(minutes / 10 + '0'));
+ buffer.append((char)(minutes % 10 + '0'));
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Inner class that acts as a compound key for time zone names.</p>
+ */
+ private static class TimeZoneDisplayKey {
+ private final TimeZone mTimeZone;
+ private final int mStyle;
+ private final Locale mLocale;
+
+ /**
+ * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties.
+ *
+ * @param timeZone the time zone
+ * @param daylight adjust the style for daylight saving time if {@code true}
+ * @param style the timezone style
+ * @param locale the timezone locale
+ */
+ TimeZoneDisplayKey(TimeZone timeZone,
+ boolean daylight, int style, Locale locale) {
+ mTimeZone = timeZone;
+ if (daylight) {
+ style |= 0x80000000;
+ }
+ mStyle = style;
+ mLocale = locale;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TimeZoneDisplayKey) {
+ TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
+ return
+ mTimeZone.equals(other.mTimeZone) &&
+ mStyle == other.mStyle &&
+ mLocale.equals(other.mLocale);
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/org/apache/commons/lang3/time/FormatCache.java b/src/org/apache/commons/lang3/time/FormatCache.java
new file mode 100644
index 0000000..19ee53a
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/FormatCache.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+import java.text.DateFormat;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>FormatCache is a cache and factory for {@link Format}s.</p>
+ *
+ * @since 3.0
+ * @version $Id: FormatCache 892161 2009-12-18 07:21:10Z $
+ */
+// TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach.
+abstract class FormatCache<F extends Format> {
+ /**
+ * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
+ */
+ static final int NONE= -1;
+
+ private final ConcurrentMap<MultipartKey, F> cInstanceCache
+ = new ConcurrentHashMap<MultipartKey, F>(7);
+
+ private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
+ = new ConcurrentHashMap<MultipartKey, String>(7);
+
+ /**
+ * <p>Gets a formatter instance using the default pattern in the
+ * default timezone and locale.</p>
+ *
+ * @return a date/time formatter
+ */
+ public F getInstance() {
+ return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern, time zone
+ * and locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone the non-null time zone
+ * @param locale the non-null locale
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ * or <code>null</code>
+ */
+ public F getInstance(String pattern, TimeZone timeZone, Locale locale) {
+ if (pattern == null) {
+ throw new NullPointerException("pattern must not be null");
+ }
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ MultipartKey key = new MultipartKey(pattern, timeZone, locale);
+ F format = cInstanceCache.get(key);
+ if (format == null) {
+ format = createInstance(pattern, timeZone, locale);
+ F previousValue= cInstanceCache.putIfAbsent(key, format);
+ if (previousValue != null) {
+ // another thread snuck in and did the same work
+ // we should return the instance that is in ConcurrentMap
+ format= previousValue;
+ }
+ }
+ return format;
+ }
+
+ /**
+ * <p>Create a format instance using the specified pattern, time zone
+ * and locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
+ * @param timeZone time zone, this will not be null.
+ * @param locale locale, this will not be null.
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ * or <code>null</code>
+ */
+ abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);
+
+ /**
+ * <p>Gets a date/time formatter instance using the specified style,
+ * time zone and locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ */
+ public F getDateTimeInstance(Integer dateStyle, Integer timeStyle, TimeZone timeZone, Locale locale) {
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
+
+ String pattern = cDateTimeInstanceCache.get(key);
+ if (pattern == null) {
+ try {
+ DateFormat formatter;
+ if (dateStyle == null) {
+ formatter = DateFormat.getTimeInstance(timeStyle, locale);
+ }
+ else if (timeStyle == null) {
+ formatter = DateFormat.getDateInstance(dateStyle, locale);
+ }
+ else {
+ formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
+ }
+ pattern = ((SimpleDateFormat)formatter).toPattern();
+ String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
+ if (previous != null) {
+ // even though it doesn't matter if another thread put the pattern
+ // it's still good practice to return the String instance that is
+ // actually in the ConcurrentMap
+ pattern= previous;
+ }
+ } catch (ClassCastException ex) {
+ throw new IllegalArgumentException("No date time pattern for locale: " + locale);
+ }
+ }
+
+ return getInstance(pattern, timeZone, locale);
+ }
+
+ // ----------------------------------------------------------------------
+ /**
+ * <p>Helper class to hold multi-part Map keys</p>
+ */
+ private static class MultipartKey {
+ private final Object[] keys;
+ private int hashCode;
+
+ /**
+ * Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
+ * @param keys the set of objects that make up the key. Each key may be null.
+ */
+ public MultipartKey(Object... keys) {
+ this.keys = keys;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ( obj instanceof MultipartKey == false ) {
+ return false;
+ }
+ return Arrays.equals(keys, ((MultipartKey)obj).keys);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ if(hashCode==0) {
+ int rc= 0;
+ for(Object key : keys) {
+ if(key!=null) {
+ rc= rc*7 + key.hashCode();
+ }
+ }
+ hashCode= rc;
+ }
+ return hashCode;
+ }
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/time/StopWatch.java b/src/org/apache/commons/lang3/time/StopWatch.java
new file mode 100644
index 0000000..f86ad85
--- /dev/null
+++ b/src/org/apache/commons/lang3/time/StopWatch.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.time;
+
+/**
+ * <p>
+ * <code>StopWatch</code> provides a convenient API for timings.
+ * </p>
+ *
+ * <p>
+ * To start the watch, call {@link #start()}. At this point you can:
+ * </p>
+ * <ul>
+ * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
+ * remove the effect of the split. At this point, these three options are available again.</li>
+ * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
+ * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
+ * <li>{@link #stop()} the watch to complete the timing session.</li>
+ * </ul>
+ *
+ * <p>
+ * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
+ * split or suspend, however a suitable result will be returned at other points.
+ * </p>
+ *
+ * <p>
+ * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
+ * resume before suspend or unsplit before split.
+ * </p>
+ *
+ * <p>
+ * 1. split(), suspend(), or stop() cannot be invoked twice<br />
+ * 2. unsplit() may only be called if the watch has been split()<br />
+ * 3. resume() may only be called if the watch has been suspend()<br />
+ * 4. start() cannot be called twice without calling reset()
+ * </p>
+ *
+ * <p>This class is not thread-safe</p>
+ *
+ * @since 2.0
+ * @version $Id: StopWatch.java 1088899 2011-04-05 05:31:27Z bayard $
+ */
+public class StopWatch {
+
+ private static final long NANO_2_MILLIS = 1000000L;
+
+ // running states
+ private static final int STATE_UNSTARTED = 0;
+
+ private static final int STATE_RUNNING = 1;
+
+ private static final int STATE_STOPPED = 2;
+
+ private static final int STATE_SUSPENDED = 3;
+
+ // split state
+ private static final int STATE_UNSPLIT = 10;
+
+ private static final int STATE_SPLIT = 11;
+
+ /**
+ * The current running state of the StopWatch.
+ */
+ private int runningState = STATE_UNSTARTED;
+
+ /**
+ * Whether the stopwatch has a split time recorded.
+ */
+ private int splitState = STATE_UNSPLIT;
+
+ /**
+ * The start time.
+ */
+ private long startTime;
+
+ /**
+ * The start time in Millis - nanoTime is only for elapsed time so we
+ * need to also store the currentTimeMillis to maintain the old
+ * getStartTime API.
+ */
+ private long startTimeMillis;
+
+ /**
+ * The stop time.
+ */
+ private long stopTime;
+
+ /**
+ * <p>
+ * Constructor.
+ * </p>
+ */
+ public StopWatch() {
+ super();
+ }
+
+ /**
+ * <p>
+ * Start the stopwatch.
+ * </p>
+ *
+ * <p>
+ * This method starts a new timing session, clearing any previous values.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is already running.
+ */
+ public void start() {
+ if (this.runningState == STATE_STOPPED) {
+ throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
+ }
+ if (this.runningState != STATE_UNSTARTED) {
+ throw new IllegalStateException("Stopwatch already started. ");
+ }
+ this.startTime = System.nanoTime();
+ this.startTimeMillis = System.currentTimeMillis();
+ this.runningState = STATE_RUNNING;
+ }
+
+ /**
+ * <p>
+ * Stop the stopwatch.
+ * </p>
+ *
+ * <p>
+ * This method ends a new timing session, allowing the time to be retrieved.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not running.
+ */
+ public void stop() {
+ if (this.runningState != STATE_RUNNING && this.runningState != STATE_SUSPENDED) {
+ throw new IllegalStateException("Stopwatch is not running. ");
+ }
+ if (this.runningState == STATE_RUNNING) {
+ this.stopTime = System.nanoTime();
+ }
+ this.runningState = STATE_STOPPED;
+ }
+
+ /**
+ * <p>
+ * Resets the stopwatch. Stops it if need be.
+ * </p>
+ *
+ * <p>
+ * This method clears the internal values to allow the object to be reused.
+ * </p>
+ */
+ public void reset() {
+ this.runningState = STATE_UNSTARTED;
+ this.splitState = STATE_UNSPLIT;
+ }
+
+ /**
+ * <p>
+ * Split the time.
+ * </p>
+ *
+ * <p>
+ * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
+ * enabling {@link #unsplit()} to continue the timing from the original start point.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not running.
+ */
+ public void split() {
+ if (this.runningState != STATE_RUNNING) {
+ throw new IllegalStateException("Stopwatch is not running. ");
+ }
+ this.stopTime = System.nanoTime();
+ this.splitState = STATE_SPLIT;
+ }
+
+ /**
+ * <p>
+ * Remove a split.
+ * </p>
+ *
+ * <p>
+ * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
+ * continue.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not been split.
+ */
+ public void unsplit() {
+ if (this.splitState != STATE_SPLIT) {
+ throw new IllegalStateException("Stopwatch has not been split. ");
+ }
+ this.splitState = STATE_UNSPLIT;
+ }
+
+ /**
+ * <p>
+ * Suspend the stopwatch for later resumption.
+ * </p>
+ *
+ * <p>
+ * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
+ * resume calls in the total time.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not currently running.
+ */
+ public void suspend() {
+ if (this.runningState != STATE_RUNNING) {
+ throw new IllegalStateException("Stopwatch must be running to suspend. ");
+ }
+ this.stopTime = System.nanoTime();
+ this.runningState = STATE_SUSPENDED;
+ }
+
+ /**
+ * <p>
+ * Resume the stopwatch after a suspend.
+ * </p>
+ *
+ * <p>
+ * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
+ * resume calls in the total time.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not been suspended.
+ */
+ public void resume() {
+ if (this.runningState != STATE_SUSPENDED) {
+ throw new IllegalStateException("Stopwatch must be suspended to resume. ");
+ }
+ this.startTime += (System.nanoTime() - this.stopTime);
+ this.runningState = STATE_RUNNING;
+ }
+
+ /**
+ * <p>
+ * Get the time on the stopwatch.
+ * </p>
+ *
+ * <p>
+ * This is either the time between the start and the moment this method is called, or the amount of time between
+ * start and stop.
+ * </p>
+ *
+ * @return the time in milliseconds
+ */
+ public long getTime() {
+ return getNanoTime() / NANO_2_MILLIS;
+ }
+ /**
+ * <p>
+ * Get the time on the stopwatch in nanoseconds.
+ * </p>
+ *
+ * <p>
+ * This is either the time between the start and the moment this method is called, or the amount of time between
+ * start and stop.
+ * </p>
+ *
+ * @return the time in nanoseconds
+ * @since 3.0
+ */
+ public long getNanoTime() {
+ if (this.runningState == STATE_STOPPED || this.runningState == STATE_SUSPENDED) {
+ return this.stopTime - this.startTime;
+ } else if (this.runningState == STATE_UNSTARTED) {
+ return 0;
+ } else if (this.runningState == STATE_RUNNING) {
+ return System.nanoTime() - this.startTime;
+ }
+ throw new RuntimeException("Illegal running state has occured. ");
+ }
+
+ /**
+ * <p>
+ * Get the split time on the stopwatch.
+ * </p>
+ *
+ * <p>
+ * This is the time between start and latest split.
+ * </p>
+ *
+ * @return the split time in milliseconds
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not yet been split.
+ * @since 2.1
+ */
+ public long getSplitTime() {
+ return getSplitNanoTime() / NANO_2_MILLIS;
+ }
+ /**
+ * <p>
+ * Get the split time on the stopwatch in nanoseconds.
+ * </p>
+ *
+ * <p>
+ * This is the time between start and latest split.
+ * </p>
+ *
+ * @return the split time in nanoseconds
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not yet been split.
+ * @since 3.0
+ */
+ public long getSplitNanoTime() {
+ if (this.splitState != STATE_SPLIT) {
+ throw new IllegalStateException("Stopwatch must be split to get the split time. ");
+ }
+ return this.stopTime - this.startTime;
+ }
+
+ /**
+ * Returns the time this stopwatch was started.
+ *
+ * @return the time this stopwatch was started
+ * @throws IllegalStateException
+ * if this StopWatch has not been started
+ * @since 2.4
+ */
+ public long getStartTime() {
+ if (this.runningState == STATE_UNSTARTED) {
+ throw new IllegalStateException("Stopwatch has not been started");
+ }
+ // System.nanoTime is for elapsed time
+ return this.startTimeMillis;
+ }
+
+ /**
+ * <p>
+ * Gets a summary of the time that the stopwatch recorded as a string.
+ * </p>
+ *
+ * <p>
+ * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
+ * </p>
+ *
+ * @return the time as a String
+ */
+ @Override
+ public String toString() {
+ return DurationFormatUtils.formatDurationHMS(getTime());
+ }
+
+ /**
+ * <p>
+ * Gets a summary of the split time that the stopwatch recorded as a string.
+ * </p>
+ *
+ * <p>
+ * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
+ * </p>
+ *
+ * @return the split time as a String
+ * @since 2.1
+ */
+ public String toSplitString() {
+ return DurationFormatUtils.formatDurationHMS(getSplitTime());
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/tuple/ImmutablePair.java b/src/org/apache/commons/lang3/tuple/ImmutablePair.java
new file mode 100644
index 0000000..8e88a43
--- /dev/null
+++ b/src/org/apache/commons/lang3/tuple/ImmutablePair.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.tuple;
+
+/**
+ * <p>An immutable pair consisting of two {@code Object} elements.</p>
+ *
+ * <p>Although the implementation is immutable, there is no restriction on the objects
+ * that may be stored. If mutable objects are stored in the pair, then the pair
+ * itself effectively becomes mutable. The class is also not {@code final}, so a subclass
+ * could add undesirable behaviour.</p>
+ *
+ * <p>#ThreadSafe# if the objects are threadsafe</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since Lang 3.0
+ * @version $Id: ImmutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $
+ */
+public final class ImmutablePair<L, R> extends Pair<L, R> {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /** Left object */
+ public final L left;
+ /** Right object */
+ public final R right;
+
+ /**
+ * <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> ImmutablePair<L, R> of(L left, R right) {
+ return new ImmutablePair<L, R>(left, right);
+ }
+
+ /**
+ * Create a new pair instance.
+ *
+ * @param left the left value, may be null
+ * @param right the right value, may be null
+ */
+ public ImmutablePair(L left, R right) {
+ super();
+ this.left = left;
+ this.right = right;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * <p>Throws {@code UnsupportedOperationException}.</p>
+ *
+ * <p>This pair is immutable, so this operation is not supported.</p>
+ *
+ * @param value the value to set
+ * @return never
+ * @throws UnsupportedOperationException as this operation is not supported
+ */
+ public R setValue(R value) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/tuple/MutablePair.java b/src/org/apache/commons/lang3/tuple/MutablePair.java
new file mode 100644
index 0000000..9864c02
--- /dev/null
+++ b/src/org/apache/commons/lang3/tuple/MutablePair.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.tuple;
+
+/**
+ * <p>A mutable pair consisting of two {@code Object} elements.</p>
+ *
+ * <p>Not #ThreadSafe#</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since Lang 3.0
+ * @version $Id: MutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $
+ */
+public class MutablePair<L, R> extends Pair<L, R> {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /** Left object */
+ public L left;
+ /** Right object */
+ public R right;
+
+ /**
+ * <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> MutablePair<L, R> of(L left, R right) {
+ return new MutablePair<L, R>(left, right);
+ }
+
+ /**
+ * Create a new pair instance of two nulls.
+ */
+ public MutablePair() {
+ super();
+ }
+
+ /**
+ * Create a new pair instance.
+ *
+ * @param left the left value, may be null
+ * @param right the right value, may be null
+ */
+ public MutablePair(L left, R right) {
+ super();
+ this.left = left;
+ this.right = right;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * Sets the left element of the pair.
+ *
+ * @param left the new value of the left element, may be null
+ */
+ public void setLeft(L left) {
+ this.left = left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * Sets the right element of the pair.
+ *
+ * @param right the new value of the right element, may be null
+ */
+ public void setRight(R right) {
+ this.right = right;
+ }
+
+ /**
+ * Sets the {@code Map.Entry} value.
+ * This sets the right element of the pair.
+ *
+ * @param value the right value to set, not null
+ * @return the old value for the right element
+ */
+ public R setValue(R value) {
+ R result = getRight();
+ setRight(value);
+ return result;
+ }
+
+}
diff --git a/src/org/apache/commons/lang3/tuple/Pair.java b/src/org/apache/commons/lang3/tuple/Pair.java
new file mode 100644
index 0000000..cd24e79
--- /dev/null
+++ b/src/org/apache/commons/lang3/tuple/Pair.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.lang3.tuple;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.builder.CompareToBuilder;
+
+/**
+ * <p>A pair consisting of two elements.</p>
+ *
+ * <p>This class is an abstract implementation defining the basic API.
+ * It refers to the elements as 'left' and 'right'. It also implements the
+ * {@code Map.Entry} interface where the key is 'left' and the value is 'right'.</p>
+ *
+ * <p>Subclass implementations may be mutable or immutable.
+ * However, there is no restriction on the type of the stored objects that may be stored.
+ * If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since Lang 3.0
+ * @version $Id: Pair.java 1142401 2011-07-03 08:30:12Z bayard $
+ */
+public abstract class Pair<L, R> implements Map.Entry<L, R>, Comparable<Pair<L, R>>, Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /**
+ * <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> Pair<L, R> of(L left, R right) {
+ return new ImmutablePair<L, R>(left, right);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the left element from this pair.</p>
+ *
+ * <p>When treated as a key-value pair, this is the key.</p>
+ *
+ * @return the left element, may be null
+ */
+ public abstract L getLeft();
+
+ /**
+ * <p>Gets the right element from this pair.</p>
+ *
+ * <p>When treated as a key-value pair, this is the value.</p>
+ *
+ * @return the right element, may be null
+ */
+ public abstract R getRight();
+
+ /**
+ * <p>Gets the key from this pair.</p>
+ *
+ * <p>This method implements the {@code Map.Entry} interface returning the
+ * left element as the key.</p>
+ *
+ * @return the left element as the key, may be null
+ */
+ public final L getKey() {
+ return getLeft();
+ }
+
+ /**
+ * <p>Gets the value from this pair.</p>
+ *
+ * <p>This method implements the {@code Map.Entry} interface returning the
+ * right element as the value.</p>
+ *
+ * @return the right element as the value, may be null
+ */
+ public R getValue() {
+ return getRight();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares the pair based on the left element followed by the right element.
+ * The types must be {@code Comparable}.</p>
+ *
+ * @param other the other pair, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ public int compareTo(Pair<L, R> other) {
+ return new CompareToBuilder().append(getLeft(), other.getLeft())
+ .append(getRight(), other.getRight()).toComparison();
+ }
+
+ /**
+ * <p>Compares this pair to another based on the two elements.</p>
+ *
+ * @param obj the object to compare to, null returns false
+ * @return true if the elements of the pair are equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map.Entry<?, ?>) {
+ Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
+ return ObjectUtils.equals(getKey(), other.getKey())
+ && ObjectUtils.equals(getValue(), other.getValue());
+ }
+ return false;
+ }
+
+ /**
+ * <p>Returns a suitable hash code.
+ * The hash code follows the definition in {@code Map.Entry}.</p>
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ // see Map.Entry API specification
+ return (getKey() == null ? 0 : getKey().hashCode()) ^
+ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ /**
+ * <p>Returns a String representation of this pair using the format {@code ($left,$right)}.</p>
+ *
+ * @return a string describing this object, not null
+ */
+ @Override
+ public String toString() {
+ return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString();
+ }
+
+ /**
+ * <p>Formats the receiver using the given format.</p>
+ *
+ * <p>This uses {@link java.util.Formattable} to perform the formatting. Two variables may
+ * be used to embed the left and right elements. Use {@code %1$s} for the left
+ * element (key) and {@code %2$s} for the right element (value).
+ * The default format used by {@code toString()} is {@code (%1$s,%2$s)}.</p>
+ *
+ * @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null
+ * @return the formatted string, not null
+ */
+ public String toString(String format) {
+ return String.format(format, getLeft(), getRight());
+ }
+
+}