From 53936c7ee6654a525075b03e1b4c8ddbfbcd4ef5 Mon Sep 17 00:00:00 2001 From: Gerald Barker Date: Fri, 2 Sep 2011 23:19:51 +0100 Subject: First commit of Apache Commons String & Date functions and required, and refactor of code to use them. --- src/org/apache/commons/lang3/ArrayUtils.java | 5796 +++++++++++++++++ .../apache/commons/lang3/CharSequenceUtils.java | 197 + src/org/apache/commons/lang3/CharUtils.java | 539 ++ src/org/apache/commons/lang3/ClassUtils.java | 1103 ++++ src/org/apache/commons/lang3/JavaVersion.java | 168 + src/org/apache/commons/lang3/ObjectUtils.java | 608 ++ src/org/apache/commons/lang3/StringUtils.java | 6557 ++++++++++++++++++++ src/org/apache/commons/lang3/SystemUtils.java | 1419 +++++ src/org/apache/commons/lang3/Validate.java | 1071 ++++ src/org/apache/commons/lang3/builder/Builder.java | 89 + .../commons/lang3/builder/CompareToBuilder.java | 1019 +++ .../commons/lang3/builder/EqualsBuilder.java | 944 +++ .../commons/lang3/builder/HashCodeBuilder.java | 961 +++ src/org/apache/commons/lang3/builder/IDKey.java | 74 + .../lang3/builder/ReflectionToStringBuilder.java | 697 +++ .../lang3/builder/StandardToStringStyle.java | 560 ++ .../commons/lang3/builder/ToStringBuilder.java | 1079 ++++ .../commons/lang3/builder/ToStringStyle.java | 2271 +++++++ .../lang3/exception/CloneFailedException.java | 62 + .../lang3/exception/ContextedException.java | 246 + .../lang3/exception/ContextedRuntimeException.java | 247 + .../lang3/exception/DefaultExceptionContext.java | 158 + .../commons/lang3/exception/ExceptionContext.java | 103 + .../commons/lang3/exception/ExceptionUtils.java | 697 +++ src/org/apache/commons/lang3/mutable/Mutable.java | 54 + .../commons/lang3/mutable/MutableBoolean.java | 193 + .../apache/commons/lang3/mutable/MutableByte.java | 283 + .../commons/lang3/mutable/MutableDouble.java | 312 + .../apache/commons/lang3/mutable/MutableFloat.java | 313 + .../apache/commons/lang3/mutable/MutableInt.java | 273 + .../apache/commons/lang3/mutable/MutableLong.java | 273 + .../commons/lang3/mutable/MutableObject.java | 126 + .../apache/commons/lang3/mutable/MutableShort.java | 283 + .../apache/commons/lang3/time/DateFormatUtils.java | 320 + src/org/apache/commons/lang3/time/DateUtils.java | 1831 ++++++ .../commons/lang3/time/DurationFormatUtils.java | 662 ++ .../apache/commons/lang3/time/FastDateFormat.java | 1519 +++++ src/org/apache/commons/lang3/time/FormatCache.java | 202 + src/org/apache/commons/lang3/time/StopWatch.java | 382 ++ .../apache/commons/lang3/tuple/ImmutablePair.java | 103 + .../apache/commons/lang3/tuple/MutablePair.java | 123 + src/org/apache/commons/lang3/tuple/Pair.java | 176 + 42 files changed, 34093 insertions(+) create mode 100644 src/org/apache/commons/lang3/ArrayUtils.java create mode 100644 src/org/apache/commons/lang3/CharSequenceUtils.java create mode 100644 src/org/apache/commons/lang3/CharUtils.java create mode 100644 src/org/apache/commons/lang3/ClassUtils.java create mode 100644 src/org/apache/commons/lang3/JavaVersion.java create mode 100644 src/org/apache/commons/lang3/ObjectUtils.java create mode 100644 src/org/apache/commons/lang3/StringUtils.java create mode 100644 src/org/apache/commons/lang3/SystemUtils.java create mode 100644 src/org/apache/commons/lang3/Validate.java create mode 100644 src/org/apache/commons/lang3/builder/Builder.java create mode 100644 src/org/apache/commons/lang3/builder/CompareToBuilder.java create mode 100644 src/org/apache/commons/lang3/builder/EqualsBuilder.java create mode 100644 src/org/apache/commons/lang3/builder/HashCodeBuilder.java create mode 100644 src/org/apache/commons/lang3/builder/IDKey.java create mode 100644 src/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java create mode 100644 src/org/apache/commons/lang3/builder/StandardToStringStyle.java create mode 100644 src/org/apache/commons/lang3/builder/ToStringBuilder.java create mode 100644 src/org/apache/commons/lang3/builder/ToStringStyle.java create mode 100644 src/org/apache/commons/lang3/exception/CloneFailedException.java create mode 100644 src/org/apache/commons/lang3/exception/ContextedException.java create mode 100644 src/org/apache/commons/lang3/exception/ContextedRuntimeException.java create mode 100644 src/org/apache/commons/lang3/exception/DefaultExceptionContext.java create mode 100644 src/org/apache/commons/lang3/exception/ExceptionContext.java create mode 100644 src/org/apache/commons/lang3/exception/ExceptionUtils.java create mode 100644 src/org/apache/commons/lang3/mutable/Mutable.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableBoolean.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableByte.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableDouble.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableFloat.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableInt.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableLong.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableObject.java create mode 100644 src/org/apache/commons/lang3/mutable/MutableShort.java create mode 100644 src/org/apache/commons/lang3/time/DateFormatUtils.java create mode 100644 src/org/apache/commons/lang3/time/DateUtils.java create mode 100644 src/org/apache/commons/lang3/time/DurationFormatUtils.java create mode 100644 src/org/apache/commons/lang3/time/FastDateFormat.java create mode 100644 src/org/apache/commons/lang3/time/FormatCache.java create mode 100644 src/org/apache/commons/lang3/time/StopWatch.java create mode 100644 src/org/apache/commons/lang3/tuple/ImmutablePair.java create mode 100644 src/org/apache/commons/lang3/tuple/MutablePair.java create mode 100644 src/org/apache/commons/lang3/tuple/Pair.java (limited to 'src/org/apache/commons') 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; + +/** + *

Operations on arrays, primitive arrays (like {@code int[]}) and + * primitive wrapper arrays (like {@code Integer[]}).

+ * + *

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.

+ * + *

#ThreadSafe#

+ * @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; + + /** + *

ArrayUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as ArrayUtils.clone(new int[] {2}).

+ * + *

This constructor is public to permit tools that require a JavaBean instance + * to operate.

+ */ + public ArrayUtils() { + super(); + } + + + // NOTE: Cannot use {@code} to enclose text which includes {}, but is OK + + + // Basic methods handling multi-dimensional arrays + //----------------------------------------------------------------------- + /** + *

Outputs an array as a String, treating {@code null} as an empty array.

+ * + *

Multi-dimensional arrays are handled correctly, including + * multi-dimensional primitive arrays.

+ * + *

The format is that of Java source code, for example {a,b}.

+ * + * @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, "{}"); + } + + /** + *

Outputs an array as a String handling {@code null}s.

+ * + *

Multi-dimensional arrays are handled correctly, including + * multi-dimensional primitive arrays.

+ * + *

The format is that of Java source code, for example {a,b}.

+ * + * @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(); + } + + /** + *

Get a hash code for an array handling multi-dimensional arrays correctly.

+ * + *

Multi-dimensional primitive arrays are also handled correctly by this method.

+ * + * @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(); + } + + /** + *

Compares two arrays, using equals(), handling multi-dimensional arrays + * correctly.

+ * + *

Multi-dimensional primitive arrays are also handled correctly by this method.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

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.

+ * + *

This method can be used to initialize:

+ *
+     * // Create a Map mapping colors.
+     * Map colorMap = MapUtils.toMap(new String[][] {{
+     *     {"RED", "#FF0000"},
+     *     {"GREEN", "#00FF00"},
+     *     {"BLUE", "#0000FF"}});
+     * 
+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 toMap(Object[] array) { + if (array == null) { + return null; + } + final Map map = new HashMap((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 + //----------------------------------------------------------------------- + /** + *

Create a type-safe generic array.

+ * + *

The Java language does not allow an array to be created from a generic type:

+ * + *
+    public static <T> T[] createAnArray(int size) {
+        return new T[size]; // compiler error here
+    }
+    public static <T> T[] createAnArray(int size) {
+        return (T[])new Object[size]; // ClassCastException at runtime
+    }
+     * 
+ * + *

Therefore new arrays of generic types can be created with this method. + * For example, an array of Strings can be created:

+ * + *
+    String[] array = ArrayUtils.toArray("1", "2");
+    String[] emptyArray = ArrayUtils.<String>toArray();
+     * 
+ * + *

The method is typically used in scenarios, where the caller itself uses generic types + * that have to be combined into an array.

+ * + *

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 + * Number[] array = ArrayUtils.<Number>toArray(Integer.valueOf(42), Double.valueOf(Math.PI)), + * there is no real advantage when compared to + * new Number[] {Integer.valueOf(42), Double.valueOf(Math.PI)}.

+ * + * @param 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[] toArray(final T... items) { + return items; + } + + // Clone + //----------------------------------------------------------------------- + /** + *

Shallow clones an array returning a typecast result and handling + * {@code null}.

+ * + *

The objects in the array are not cloned, thus there is no special + * handling for multi-dimensional arrays.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @param 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[] clone(T[] array) { + if (array == null) { + return null; + } + return array.clone(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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(); + } + + /** + *

Clones an array returning a typecast result and handling + * {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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; + } + + /** + *

Defensive programming technique to change a {@code null} + * reference to an empty one.

+ * + *

This method returns an empty array for a {@code null} input array.

+ * + *

As a memory optimizing technique an empty array passed in will be overridden with + * the empty {@code public static} references in this class.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Produces a new array containing the elements between + * the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + *

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:

+ * + *
+     * Date[] someDates = (Date[])ArrayUtils.subarray(allDates, 2, 5);
+     * 
+ * + * @param the component type of the array + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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[] 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; + } + + /** + *

Produces a new {@code long} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code int} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code short} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code char} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code byte} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code double} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code float} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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; + } + + /** + *

Produces a new {@code boolean} array containing the elements + * between the start and end indices.

+ * + *

The start index is inclusive, the end index exclusive. + * Null array input produces null output.

+ * + * @param array the array + * @param startIndexInclusive the starting index. Undervalue (<0) + * is promoted to 0, overvalue (>array.length) results + * in an empty array. + * @param endIndexExclusive elements up to endIndex-1 are present in the + * returned subarray. Undervalue (< startIndex) produces + * empty array, overvalue (>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 + //----------------------------------------------------------------------- + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}. + * + *

Any multi-dimensional aspects of the arrays are ignored.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + /** + *

Checks whether two arrays are the same length, treating + * {@code null} arrays as length {@code 0}.

+ * + * @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; + } + + //----------------------------------------------------------------------- + /** + *

Returns the length of the specified array. + * This method can deal with {@code Object} arrays and with primitive arrays.

+ * + *

If the input array is {@code null}, {@code 0} is returned.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Checks whether two arrays are the same type taking into account + * multi-dimensional arrays.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Reverses the order of the given array.

+ * + *

There is no special handling for multi-dimensional arrays.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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++; + } + } + + /** + *

Reverses the order of the given array.

+ * + *

This method does nothing for a {@code null} input array.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given object in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given object in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given object within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given object in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the object is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

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.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

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.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

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.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

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.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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; + } + + /** + *

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).

+ * + *

The method returns {@code false} if a {@code null} array + * is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the index of the given value in the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + * @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); + } + + /** + *

Finds the index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

A negative startIndex is treated as zero. A startIndex larger than the array + * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).

+ * + * @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; + } + + /** + *

Finds the last index of the given value within the array.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) if + * {@code null} array input.

+ * + * @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); + } + + /** + *

Finds the last index of the given value in the array starting at the given index.

+ * + *

This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.

+ * + *

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.

+ * + * @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; + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns {@code false} if a {@code null} array is passed in.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Characters to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Character to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive chars to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Longs to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Long to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive longs to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Integers to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Integer to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive ints to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Shorts to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Short to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive shorts to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Bytes to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Bytes to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive bytes to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Doubles to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Doubles to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive doubles to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Floats to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Floats to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive floats to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Converts an array of object Booleans to primitives.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of object Booleans to primitives handling {@code null}.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + /** + *

Converts an array of primitive booleans to objects.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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; + } + + // ---------------------------------------------------------------------- + /** + *

Checks if an array of Objects is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive longs is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive ints is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive shorts is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive chars is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive bytes is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive doubles is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive floats is empty or {@code null}.

+ * + * @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; + } + + /** + *

Checks if an array of primitive booleans is empty or {@code null}.

+ * + * @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; + } + + // ---------------------------------------------------------------------- + /** + *

Checks if an array of Objects is not empty or not {@code null}.

+ * + * @param 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 boolean isNotEmpty(T[] array) { + return (array != null && array.length != 0); + } + + /** + *

Checks if an array of primitive longs is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive ints is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive shorts is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive chars is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive bytes is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive doubles is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive floats is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Checks if an array of primitive booleans is not empty or not {@code null}.

+ * + * @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); + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * 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"]
+     * 
+ * + * @param 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[] 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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Adds all the elements of the given arrays into a new array.

+ *

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.

+ * + *
+     * ArrayUtils.addAll(array1, null)   = cloned copy of array1
+     * ArrayUtils.addAll(null, array2)   = cloned copy of array2
+     * ArrayUtils.addAll([], [])         = []
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

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[]

+ * + *
+     * 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"]
+     * 
+ * + * @param 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[] 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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, true)          = [true]
+     * ArrayUtils.add([true], false)       = [true, false]
+     * ArrayUtils.add([true, false], true) = [true, false, true]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, '0')       = ['0']
+     * ArrayUtils.add(['1'], '0')      = ['1', '0']
+     * ArrayUtils.add(['1', '0'], '1') = ['1', '0', '1']
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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; + } + + /** + *

Copies the given array and adds the given element at the end of the new array.

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * ArrayUtils.add(null, 0)   = [0]
+     * ArrayUtils.add([1], 0)    = [1, 0]
+     * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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"]
+     * 
+ * + * @param 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[] 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; + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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']
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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).

+ * + *

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.

+ * + *

If the input array is {@code null}, a new one element array is returned + * whose component type is the same as the element.

+ * + *
+     * 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]
+     * 
+ * + * @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; + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove(["a"], 0)           = []
+     * ArrayUtils.remove(["a", "b"], 0)      = ["b"]
+     * ArrayUtils.remove(["a", "b"], 1)      = ["a"]
+     * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
+     * 
+ * + * @param 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[] remove(T[] array, int index) { + return (T[]) remove((Object) array, index); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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"]
+     * 
+ * + * @param 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[] removeElement(T[] array, Object element) { + int index = indexOf(array, element); + if (index == INDEX_NOT_FOUND) { + return clone(array); + } + return remove(array, index); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove([true], 0)              = []
+     * ArrayUtils.remove([true, false], 0)       = [false]
+     * ArrayUtils.remove([true, false], 1)       = [true]
+     * ArrayUtils.remove([true, true, false], 1) = [true, false]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove([1], 0)          = []
+     * ArrayUtils.remove([1, 0], 0)       = [0]
+     * ArrayUtils.remove([1, 0], 1)       = [1]
+     * ArrayUtils.remove([1, 0, 1], 1)    = [1, 1]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove(['a'], 0)           = []
+     * ArrayUtils.remove(['a', 'b'], 0)      = ['b']
+     * ArrayUtils.remove(['a', 'b'], 1)      = ['a']
+     * ArrayUtils.remove(['a', 'b', 'c'], 1) = ['a', 'c']
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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']
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.remove([1], 0)         = []
+     * ArrayUtils.remove([2, 6], 0)      = [6]
+     * ArrayUtils.remove([2, 6], 1)      = [2]
+     * ArrayUtils.remove([2, 6, 3], 1)   = [2, 3]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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); + } + + /** + *

Removes the element at the specified position from the specified array. + * All subsequent elements are shifted to the left (subtracts one from + * their indices).

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + * @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; + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.removeAll(["a", "b", "c"], 0, 2) = ["b"]
+     * ArrayUtils.removeAll(["a", "b", "c"], 1, 2) = ["a"]
+     * 
+ * + * @param 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[] removeAll(T[] array, int... indices) { + return (T[]) removeAll((Object) array, clone(indices)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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"]
+     * 
+ * + * @param 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[] removeElements(T[] array, T... values) { + if (isEmpty(array) || isEmpty(values)) { + return clone(array); + } + HashMap occurrences = new HashMap(values.length); + for (T v : values) { + MutableInt count = occurrences.get(v); + if (count == null) { + occurrences.put(v, new MutableInt(1)); + } else { + count.increment(); + } + } + HashSet toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * 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) = []
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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)); + } + + /** + *

Removes the elements at the specified positions from the specified array. + * All remaining elements are shifted to the left.

+ * + *

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.

+ * + *

If the input array is {@code null}, an IndexOutOfBoundsException + * will be thrown, because in that case no valid index can be specified.

+ * + *
+     * ArrayUtils.removeAll([true, false, true], 0, 2) = [false]
+     * ArrayUtils.removeAll([true, false, true], 1, 2) = [true]
+     * 
+ * + * @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)); + } + + /** + *

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.

+ * + *

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.

+ * + *
+     * 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]
+     * 
+ * + * @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 occurrences = new HashMap(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 toRemove = new HashSet(); + for (Map.Entry 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 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; + +/** + *

Operations on {@link java.lang.CharSequence} that are + * {@code null} safe.

+ * + * @see java.lang.CharSequence + * @since 3.0 + * @version $Id$ + */ +public class CharSequenceUtils { + + /** + *

{@code CharSequenceUtils} instances should NOT be constructed in + * standard programming.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public CharSequenceUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

Returns a new {@code CharSequence} that is a subsequence of this + * sequence starting with the {@code char} value at the specified index.

+ * + *

This provides the {@code CharSequence} equivalent to {@link String#substring(int)}. + * The length (in {@code char}) of the returned sequence is {@code length() - start}, + * so if {@code start == end} then an empty sequence is returned.

+ * + * @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()); + } + + //----------------------------------------------------------------------- + /** + *

Finds the first index in the {@code CharSequence} that matches the + * specified character.

+ * + * @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); +// } + } + + /** + *

Finds the last index in the {@code CharSequence} that matches the + * specified character.

+ * + * @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; + +/** + *

Operations on char primitives and Character objects.

+ * + *

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.

+ * + *

#ThreadSafe#

+ * @since 2.1 + * @version $Id$ + */ +public class CharUtils { + + private static final String[] CHAR_STRING_ARRAY = new String[128]; + + /** + * {@code \u000a} linefeed LF ('\n'). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @since 2.2 + */ + public static final char LF = '\n'; + + /** + * {@code \u000d} carriage return CR ('\r'). + * + * @see JLF: Escape Sequences + * for Character and String Literals + * @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); + } + } + + /** + *

{@code CharUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code CharUtils.toString('c');}.

+ * + *

This constructor is public to permit tools that require a JavaBean instance + * to operate.

+ */ + public CharUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

Converts the character to a Character.

+ * + *

For ASCII 7 bit characters, this uses a cache that will return the + * same Character object each time.

+ * + *
+     *   CharUtils.toCharacterObject(' ')  = ' '
+     *   CharUtils.toCharacterObject('A')  = 'A'
+     * 
+ * + * @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); + } + + /** + *

Converts the String to a Character using the first character, returning + * null for empty Strings.

+ * + *

For ASCII 7 bit characters, this uses a cache that will return the + * same Character object each time.

+ * + *
+     *   CharUtils.toCharacterObject(null) = null
+     *   CharUtils.toCharacterObject("")   = null
+     *   CharUtils.toCharacterObject("A")  = 'A'
+     *   CharUtils.toCharacterObject("BA") = 'B'
+     * 
+ * + * @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)); + } + + //----------------------------------------------------------------------- + /** + *

Converts the Character to a char throwing an exception for {@code null}.

+ * + *
+     *   CharUtils.toChar(' ')  = ' '
+     *   CharUtils.toChar('A')  = 'A'
+     *   CharUtils.toChar(null) throws IllegalArgumentException
+     * 
+ * + * @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(); + } + + /** + *

Converts the Character to a char handling {@code null}.

+ * + *
+     *   CharUtils.toChar(null, 'X') = 'X'
+     *   CharUtils.toChar(' ', 'X')  = ' '
+     *   CharUtils.toChar('A', 'X')  = 'A'
+     * 
+ * + * @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(); + } + + //----------------------------------------------------------------------- + /** + *

Converts the String to a char using the first character, throwing + * an exception on empty Strings.

+ * + *
+     *   CharUtils.toChar("A")  = 'A'
+     *   CharUtils.toChar("BA") = 'B'
+     *   CharUtils.toChar(null) throws IllegalArgumentException
+     *   CharUtils.toChar("")   throws IllegalArgumentException
+     * 
+ * + * @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); + } + + /** + *

Converts the String to a char using the first character, defaulting + * the value on empty Strings.

+ * + *
+     *   CharUtils.toChar(null, 'X') = 'X'
+     *   CharUtils.toChar("", 'X')   = 'X'
+     *   CharUtils.toChar("A", 'X')  = 'A'
+     *   CharUtils.toChar("BA", 'X') = 'B'
+     * 
+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

+ * + *

This method coverts the char '1' to the int 1 and so on.

+ * + *
+     *   CharUtils.toIntValue('3')  = 3
+     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
+     * 
+ * + * @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; + } + + /** + *

Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

+ * + *

This method coverts the char '1' to the int 1 and so on.

+ * + *
+     *   CharUtils.toIntValue('3', -1)  = 3
+     *   CharUtils.toIntValue('A', -1)  = -1
+     * 
+ * + * @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; + } + + /** + *

Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

+ * + *

This method coverts the char '1' to the int 1 and so on.

+ * + *
+     *   CharUtils.toIntValue('3')  = 3
+     *   CharUtils.toIntValue(null) throws IllegalArgumentException
+     *   CharUtils.toIntValue('A')  throws IllegalArgumentException
+     * 
+ * + * @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()); + } + + /** + *

Converts the character to the Integer it represents, throwing an + * exception if the character is not numeric.

+ * + *

This method coverts the char '1' to the int 1 and so on.

+ * + *
+     *   CharUtils.toIntValue(null, -1) = -1
+     *   CharUtils.toIntValue('3', -1)  = 3
+     *   CharUtils.toIntValue('A', -1)  = -1
+     * 
+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Converts the character to a String that contains the one character.

+ * + *

For ASCII 7 bit characters, this uses a cache that will return the + * same String object each time.

+ * + *
+     *   CharUtils.toString(' ')  = " "
+     *   CharUtils.toString('A')  = "A"
+     * 
+ * + * @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}); + } + + /** + *

Converts the character to a String that contains the one character.

+ * + *

For ASCII 7 bit characters, this uses a cache that will return the + * same String object each time.

+ * + *

If {@code null} is passed in, {@code null} will be returned.

+ * + *
+     *   CharUtils.toString(null) = null
+     *   CharUtils.toString(' ')  = " "
+     *   CharUtils.toString('A')  = "A"
+     * 
+ * + * @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()); + } + + //-------------------------------------------------------------------------- + /** + *

Converts the string to the Unicode format '\u0020'.

+ * + *

This format is the Java source code format.

+ * + *
+     *   CharUtils.unicodeEscaped(' ') = "\u0020"
+     *   CharUtils.unicodeEscaped('A') = "\u0041"
+     * 
+ * + * @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); + } + + /** + *

Converts the string to the Unicode format '\u0020'.

+ * + *

This format is the Java source code format.

+ * + *

If {@code null} is passed in, {@code null} will be returned.

+ * + *
+     *   CharUtils.unicodeEscaped(null) = null
+     *   CharUtils.unicodeEscaped(' ')  = "\u0020"
+     *   CharUtils.unicodeEscaped('A')  = "\u0041"
+     * 
+ * + * @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()); + } + + //-------------------------------------------------------------------------- + /** + *

Checks whether the character is ASCII 7 bit.

+ * + *
+     *   CharUtils.isAscii('a')  = true
+     *   CharUtils.isAscii('A')  = true
+     *   CharUtils.isAscii('3')  = true
+     *   CharUtils.isAscii('-')  = true
+     *   CharUtils.isAscii('\n') = true
+     *   CharUtils.isAscii('©') = false
+     * 
+ * + * @param ch the character to check + * @return true if less than 128 + */ + public static boolean isAscii(char ch) { + return ch < 128; + } + + /** + *

Checks whether the character is ASCII 7 bit printable.

+ * + *
+     *   CharUtils.isAsciiPrintable('a')  = true
+     *   CharUtils.isAsciiPrintable('A')  = true
+     *   CharUtils.isAsciiPrintable('3')  = true
+     *   CharUtils.isAsciiPrintable('-')  = true
+     *   CharUtils.isAsciiPrintable('\n') = false
+     *   CharUtils.isAsciiPrintable('©') = false
+     * 
+ * + * @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; + } + + /** + *

Checks whether the character is ASCII 7 bit control.

+ * + *
+     *   CharUtils.isAsciiControl('a')  = false
+     *   CharUtils.isAsciiControl('A')  = false
+     *   CharUtils.isAsciiControl('3')  = false
+     *   CharUtils.isAsciiControl('-')  = false
+     *   CharUtils.isAsciiControl('\n') = true
+     *   CharUtils.isAsciiControl('©') = false
+     * 
+ * + * @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; + } + + /** + *

Checks whether the character is ASCII 7 bit alphabetic.

+ * + *
+     *   CharUtils.isAsciiAlpha('a')  = true
+     *   CharUtils.isAsciiAlpha('A')  = true
+     *   CharUtils.isAsciiAlpha('3')  = false
+     *   CharUtils.isAsciiAlpha('-')  = false
+     *   CharUtils.isAsciiAlpha('\n') = false
+     *   CharUtils.isAsciiAlpha('©') = false
+     * 
+ * + * @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'); + } + + /** + *

Checks whether the character is ASCII 7 bit alphabetic upper case.

+ * + *
+     *   CharUtils.isAsciiAlphaUpper('a')  = false
+     *   CharUtils.isAsciiAlphaUpper('A')  = true
+     *   CharUtils.isAsciiAlphaUpper('3')  = false
+     *   CharUtils.isAsciiAlphaUpper('-')  = false
+     *   CharUtils.isAsciiAlphaUpper('\n') = false
+     *   CharUtils.isAsciiAlphaUpper('©') = false
+     * 
+ * + * @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'; + } + + /** + *

Checks whether the character is ASCII 7 bit alphabetic lower case.

+ * + *
+     *   CharUtils.isAsciiAlphaLower('a')  = true
+     *   CharUtils.isAsciiAlphaLower('A')  = false
+     *   CharUtils.isAsciiAlphaLower('3')  = false
+     *   CharUtils.isAsciiAlphaLower('-')  = false
+     *   CharUtils.isAsciiAlphaLower('\n') = false
+     *   CharUtils.isAsciiAlphaLower('©') = false
+     * 
+ * + * @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'; + } + + /** + *

Checks whether the character is ASCII 7 bit numeric.

+ * + *
+     *   CharUtils.isAsciiNumeric('a')  = false
+     *   CharUtils.isAsciiNumeric('A')  = false
+     *   CharUtils.isAsciiNumeric('3')  = true
+     *   CharUtils.isAsciiNumeric('-')  = false
+     *   CharUtils.isAsciiNumeric('\n') = false
+     *   CharUtils.isAsciiNumeric('©') = false
+     * 
+ * + * @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'; + } + + /** + *

Checks whether the character is ASCII 7 bit numeric.

+ * + *
+     *   CharUtils.isAsciiAlphanumeric('a')  = true
+     *   CharUtils.isAsciiAlphanumeric('A')  = true
+     *   CharUtils.isAsciiAlphanumeric('3')  = true
+     *   CharUtils.isAsciiAlphanumeric('-')  = false
+     *   CharUtils.isAsciiAlphanumeric('\n') = false
+     *   CharUtils.isAsciiAlphanumeric('©') = false
+     * 
+ * + * @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; + + +/** + *

Operates on classes without using reflection.

+ * + *

This class handles invalid {@code null} inputs as best it can. + * Each method documents its behaviour in more detail.

+ * + *

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}.

+ * + * @since 2.0 + * @version $Id: ClassUtils.java 1145035 2011-07-11 06:09:39Z bayard $ + */ +public class ClassUtils { + + /** + *

The package separator character: '.' == {@value}.

+ */ + public static final char PACKAGE_SEPARATOR_CHAR = '.'; + + /** + *

The package separator String: {@code "."}.

+ */ + public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR); + + /** + *

The inner class separator character: '$' == {@value}.

+ */ + public static final char INNER_CLASS_SEPARATOR_CHAR = '$'; + + /** + *

The inner class separator String: {@code "$"}.

+ */ + 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> primitiveWrapperMap = new HashMap, 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> wrapperPrimitiveMap = new HashMap, 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 abbreviationMap = new HashMap(); + + /** + * Maps an abbreviation used in array class names to corresponding primitive class name. + */ + private static final Map reverseAbbreviationMap = new HashMap(); + + /** + * 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"); + } + + /** + *

ClassUtils instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code ClassUtils.getShortClassName(cls)}.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public ClassUtils() { + super(); + } + + // Short class name + // ---------------------------------------------------------------------- + /** + *

Gets the class name minus the package name for an {@code Object}.

+ * + * @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()); + } + + /** + *

Gets the class name minus the package name from a {@code Class}.

+ * + *

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"}.

+ * + * @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()); + } + + /** + *

Gets the class name minus the package name from a String.

+ * + *

The string passed in is assumed to be a class name - it is not checked.

+ + *

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"}.

+ * + * @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; + } + + /** + *

Null-safe version of aClass.getSimpleName()

+ * + * @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(); + } + + /** + *

Null-safe version of aClass.getSimpleName()

+ * + * @param object the object for which to get the simple class name. + * @param valueIfNull the value to return if object is null + * @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 + // ---------------------------------------------------------------------- + /** + *

Gets the package name of an {@code Object}.

+ * + * @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()); + } + + /** + *

Gets the package name of a {@code Class}.

+ * + * @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()); + } + + /** + *

Gets the package name from a {@code String}.

+ * + *

The string passed in is assumed to be a class name - it is not checked.

+ *

If the class is unpackaged, return an empty string.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Gets a {@code List} of superclasses for the given class.

+ * + * @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> getAllSuperclasses(Class cls) { + if (cls == null) { + return null; + } + List> classes = new ArrayList>(); + Class superclass = cls.getSuperclass(); + while (superclass != null) { + classes.add(superclass); + superclass = superclass.getSuperclass(); + } + return classes; + } + + /** + *

Gets a {@code List} of all interfaces implemented by the given + * class and its superclasses.

+ * + *

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.

+ * + * @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> getAllInterfaces(Class cls) { + if (cls == null) { + return null; + } + + LinkedHashSet> interfacesFound = new LinkedHashSet>(); + getAllInterfaces(cls, interfacesFound); + + return new ArrayList>(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> interfacesFound) { + while (cls != null) { + Class[] interfaces = cls.getInterfaces(); + + for (Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + + // Convert list + // ---------------------------------------------------------------------- + /** + *

Given a {@code List} of class names, this method converts them into classes.

+ * + *

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}.

+ * + * @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> convertClassNamesToClasses(List classNames) { + if (classNames == null) { + return null; + } + List> classes = new ArrayList>(classNames.size()); + for (String className : classNames) { + try { + classes.add(Class.forName(className)); + } catch (Exception ex) { + classes.add(null); + } + } + return classes; + } + + /** + *

Given a {@code List} of {@code Class} objects, this method converts + * them into class names.

+ * + *

A new {@code List} is returned. {@code null} objects will be copied into + * the returned list as {@code null}.

+ * + * @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 convertClassesToClassNames(List> classes) { + if (classes == null) { + return null; + } + List classNames = new ArrayList(classes.size()); + for (Class cls : classes) { + if (cls == null) { + classNames.add(null); + } else { + classNames.add(cls.getName()); + } + } + return classNames; + } + + // Is assignable + // ---------------------------------------------------------------------- + /** + *

Checks if an array of Classes can be assigned to another array of Classes.

+ * + *

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).

+ * + *

Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this + * method takes into account widenings of primitive classes and + * {@code null}s.

+ * + *

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.

+ * + *

{@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.

+ * + *

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 + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + *

Since Lang 3.0, this method will default behavior for + * calculating assignability between primitive and wrapper types corresponding + * to the running Java version; i.e. autoboxing will be the default + * behavior in VMs running Java versions >= 1.5.

+ * + * @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)); + } + + /** + *

Checks if an array of Classes can be assigned to another array of Classes.

+ * + *

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).

+ * + *

Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this + * method takes into account widenings of primitive classes and + * {@code null}s.

+ * + *

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.

+ * + *

{@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.

+ * + *

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 + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + * @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; + } + + /** + *

Checks if one {@code Class} can be assigned to a variable of + * another {@code Class}.

+ * + *

Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, + * this method takes into account widenings of primitive classes and + * {@code null}s.

+ * + *

Primitive widenings allow an int to be assigned to a long, float or + * double. This method returns the correct result for these cases.

+ * + *

{@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.

+ * + *

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 + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + *

Since Lang 3.0, this method will default behavior for + * calculating assignability between primitive and wrapper types corresponding + * to the running Java version; i.e. autoboxing will be the default + * behavior in VMs running Java versions >= 1.5.

+ * + * @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)); + } + + /** + *

Checks if one {@code Class} can be assigned to a variable of + * another {@code Class}.

+ * + *

Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, + * this method takes into account widenings of primitive classes and + * {@code null}s.

+ * + *

Primitive widenings allow an int to be assigned to a long, float or + * double. This method returns the correct result for these cases.

+ * + *

{@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.

+ * + *

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 + * The Java Language Specification, + * sections 5.1.1, 5.1.2 and 5.1.4 for details.

+ * + * @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); + } + + /** + *

Converts the specified primitive Class object to its corresponding + * wrapper Class object.

+ * + *

NOTE: From v2.2, this method handles {@code Void.TYPE}, + * returning {@code Void.TYPE}.

+ * + * @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; + } + + /** + *

Converts the specified array of primitive Class objects to an array of + * its corresponding wrapper Class objects.

+ * + * @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; + } + + /** + *

Converts the specified wrapper class to its corresponding primitive + * class.

+ * + *

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 + * null, the return value is null.

+ * + * @param cls the class to convert, may be null + * @return the corresponding primitive type if {@code cls} is a + * wrapper class, null otherwise + * @see #primitiveToWrapper(Class) + * @since 2.4 + */ + public static Class wrapperToPrimitive(Class cls) { + return wrapperPrimitiveMap.get(cls); + } + + /** + *

Converts the specified array of wrapper Class objects to an array of + * its corresponding primitive Class objects.

+ * + *

This method invokes {@code wrapperToPrimitive()} for each element + * of the passed in array.

+ * + * @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 + * null 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 + // ---------------------------------------------------------------------- + /** + *

Is the specified class an inner class or static nested class.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

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 + * 4071957). + * + *

Set set = Collections.unmodifiableSet(...);
+     *  Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty",  new Class[0]);
+     *  Object result = method.invoke(set, new Object[]);
+ *

+ * + * @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> candidateClasses = new ArrayList>(); + 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; + } + + /** + *

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.

+ * + *

This method returns {@code null} for a {@code null} input array.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Gets the canonical name minus the package name for an {@code Object}.

+ * + * @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()); + } + + /** + *

Gets the canonical name minus the package name from a {@code Class}.

+ * + * @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()); + } + + /** + *

Gets the canonical name minus the package name from a String.

+ * + *

The string passed in is assumed to be a canonical name - it is not checked.

+ * + * @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 + // ---------------------------------------------------------------------- + /** + *

Gets the package name from the canonical name of an {@code Object}.

+ * + * @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()); + } + + /** + *

Gets the package name from the canonical name of a {@code Class}.

+ * + * @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()); + } + + /** + *

Gets the package name from the canonical name.

+ * + *

The string passed in is assumed to be a canonical name - it is not checked.

+ *

If the class is unpackaged, return an empty string.

+ * + * @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)); + } + + /** + *

Converts a given name of class into canonical format. + * If name of class is not a name of array class it returns + * unchanged name.

+ *

Example: + *

    + *
  • {@code getCanonicalName("[I") = "int[]"}
  • + *
  • {@code getCanonicalName("[Ljava.lang.String;") = "java.lang.String[]"}
  • + *
  • {@code getCanonicalName("java.lang.String") = "java.lang.String"}
  • + *
+ *

+ * + * @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; + +/** + *

An enum representing all the versions of the Java specification. + * This is intended to mirror available values from the + * java.specification.version System property.

+ * + * @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; + } + + //----------------------------------------------------------------------- + /** + *

Whether this version of Java is at least the version of Java passed in.

+ * + *

For example:
+ * {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}

+ * + * @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 null 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 null 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; + } + } + + //----------------------------------------------------------------------- + /** + *

The string value is overridden to return the standard name.

+ * + *

For example, "1.5".

+ * + * @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; + +/** + *

Operations on {@code Object}.

+ * + *

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.

+ * + *

#ThreadSafe#

+ * @since 1.0 + * @version $Id: ObjectUtils.java 1153350 2011-08-03 05:29:21Z bayard $ + */ +//@Immutable +public class ObjectUtils { + + /** + *

Singleton used as a {@code null} placeholder where + * {@code null} has another meaning.

+ * + *

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.

+ * + *

Another example is {@code Hashtable}, where {@code null} + * cannot be stored.

+ * + *

This instance is Serializable.

+ */ + public static final Null NULL = new Null(); + + /** + *

{@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");}.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public ObjectUtils() { + super(); + } + + // Defaulting + //----------------------------------------------------------------------- + /** + *

Returns a default value if the object passed is {@code null}.

+ * + *
+     * ObjectUtils.defaultIfNull(null, null)      = null
+     * ObjectUtils.defaultIfNull(null, "")        = ""
+     * ObjectUtils.defaultIfNull(null, "zz")      = "zz"
+     * ObjectUtils.defaultIfNull("abc", *)        = "abc"
+     * ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
+     * 
+ * + * @param 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 defaultIfNull(T object, T defaultValue) { + return object != null ? object : defaultValue; + } + + /** + *

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.

+ * + *
+     * 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
+     * 
+ * + * @param 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 firstNonNull(T... values) { + if (values != null) { + for (T val : values) { + if (val != null) { + return val; + } + } + } + return null; + } + + // Null-safe equals/hashCode + //----------------------------------------------------------------------- + /** + *

Compares two objects for equality, where either one or both + * objects may be {@code null}.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Compares two objects for inequality, where either one or both + * objects may be {@code null}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Gets the hash code of an object returning zero when the + * object is {@code null}.

+ * + *
+     * ObjectUtils.hashCode(null)   = 0
+     * ObjectUtils.hashCode(obj)    = obj.hashCode()
+     * 
+ * + * @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(); + } + + /** + *

Gets the hash code for multiple objects.

+ * + *

This allows a hash code to be rapidly calculated for a number of objects. + * The hash code for a single object is the not 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.

+ * + *
+     * 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()
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Gets the toString that would be produced by {@code Object} + * if a class did not override toString itself. {@code null} + * will return {@code null}.

+ * + *
+     * ObjectUtils.identityToString(null)         = null
+     * ObjectUtils.identityToString("")           = "java.lang.String@1e23"
+     * ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
+     * 
+ * + * @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(); + } + + /** + *

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.

+ * + *
+     * 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")
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Gets the {@code toString} of an {@code Object} returning + * an empty string ("") if {@code null} input.

+ * + *
+     * ObjectUtils.toString(null)         = ""
+     * ObjectUtils.toString("")           = ""
+     * ObjectUtils.toString("bat")        = "bat"
+     * ObjectUtils.toString(Boolean.TRUE) = "true"
+     * 
+ * + * @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(); + } + + /** + *

Gets the {@code toString} of an {@code Object} returning + * a specified text if {@code null} input.

+ * + *
+     * ObjectUtils.toString(null, null)           = null
+     * ObjectUtils.toString(null, "null")         = "null"
+     * ObjectUtils.toString("", "null")           = ""
+     * ObjectUtils.toString("bat", "null")        = "bat"
+     * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Null safe comparison of Comparables.

+ * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
    + *
  • If any objects are non-null and unequal, the lesser object. + *
  • If all objects are non-null and equal, the first. + *
  • If any of the comparables are null, the lesser of the non-null objects. + *
  • If all the comparables are null, null is returned. + *
+ */ + public static > T min(T... values) { + T result = null; + if (values != null) { + for (T value : values) { + if (compare(value, result, true) < 0) { + result = value; + } + } + } + return result; + } + + /** + *

Null safe comparison of Comparables.

+ * + * @param type of the values processed by this method + * @param values the set of comparable values, may be null + * @return + *
    + *
  • If any objects are non-null and unequal, the greater object. + *
  • If all objects are non-null and equal, the first. + *
  • If any of the comparables are null, the greater of the non-null objects. + *
  • If all the comparables are null, null is returned. + *
+ */ + public static > T max(T... values) { + T result = null; + if (values != null) { + for (T value : values) { + if (compare(value, result, false) > 0) { + result = value; + } + } + } + return result; + } + + /** + *

Null safe comparison of Comparables. + * {@code null} is assumed to be less than a non-{@code null} value.

+ * + * @param 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 > int compare(T c1, T c2) { + return compare(c1, c2, false); + } + + /** + *

Null safe comparison of Comparables.

+ * + * @param 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 > 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 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 median(T... items) { + Validate.notEmpty(items); + Validate.noNullElements(items); + TreeSet sort = new TreeSet(); + 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 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 median(Comparator comparator, T... items) { + Validate.notEmpty(items, "null/empty items"); + Validate.noNullElements(items); + Validate.notNull(comparator, "null comparator"); + TreeSet sort = new TreeSet(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 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 mode(T... items) { + if (ArrayUtils.isNotEmpty(items)) { + HashMap occurrences = new HashMap(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 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 + //----------------------------------------------------------------------- + /** + *

Clone an object.

+ * + * @param 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 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; + } + + /** + *

Clone an object if possible.

+ * + *

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.

+ * + * @param 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 cloneIfPossible(final T obj) { + final T clone = clone(obj); + return clone == null ? obj : clone; + } + + // Null + //----------------------------------------------------------------------- + /** + *

Class used as a null placeholder where {@code null} + * has another meaning.

+ * + *

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.

+ * + *

Another example is {@code Hashtable}, where {@code null} + * cannot be stored.

+ */ + 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(); + } + + /** + *

Ensure singleton.

+ * + * @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; + +/** + *

Operations on {@link java.lang.String} that are + * {@code null} safe.

+ * + *
    + *
  • IsEmpty/IsBlank + * - checks if a String contains text
  • + *
  • Trim/Strip + * - removes leading and trailing whitespace
  • + *
  • Equals + * - compares two strings null-safe
  • + *
  • startsWith + * - check if a String starts with a prefix null-safe
  • + *
  • endsWith + * - check if a String ends with a suffix null-safe
  • + *
  • IndexOf/LastIndexOf/Contains + * - null-safe index-of checks + *
  • IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut + * - index-of any of a set of Strings
  • + *
  • ContainsOnly/ContainsNone/ContainsAny + * - does String contains only/none/any of these characters
  • + *
  • Substring/Left/Right/Mid + * - null-safe substring extractions
  • + *
  • SubstringBefore/SubstringAfter/SubstringBetween + * - substring extraction relative to other strings
  • + *
  • Split/Join + * - splits a String into an array of substrings and vice versa
  • + *
  • Remove/Delete + * - removes part of a String
  • + *
  • Replace/Overlay + * - Searches a String and replaces one String with another
  • + *
  • Chomp/Chop + * - removes the last part of a String
  • + *
  • LeftPad/RightPad/Center/Repeat + * - pads a String
  • + *
  • UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize + * - changes the case of a String
  • + *
  • CountMatches + * - counts the number of occurrences of one String in another
  • + *
  • IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable + * - checks the characters in a String
  • + *
  • DefaultString + * - protects against a null input String
  • + *
  • Reverse/ReverseDelimited + * - reverses a String
  • + *
  • Abbreviate + * - abbreviates a string using ellipsis
  • + *
  • Difference + * - compares Strings and reports on their differences
  • + *
  • LevenshteinDistance + * - the number of changes needed to change one String into another
  • + *
+ * + *

The {@code StringUtils} class defines certain words related to + * String handling.

+ * + *
    + *
  • null - {@code null}
  • + *
  • empty - a zero-length string ({@code ""})
  • + *
  • space - the space character ({@code ' '}, char 32)
  • + *
  • whitespace - the characters defined by {@link Character#isWhitespace(char)}
  • + *
  • trim - the characters <= 32 as in {@link String#trim()}
  • + *
+ * + *

{@code StringUtils} handles {@code null} input Strings quietly. + * That is to say that a {@code null} input will return {@code null}. + * Where a {@code boolean} or {@code int} is being returned + * details vary by method.

+ * + *

A side effect of the {@code null} handling is that a + * {@code NullPointerException} should be considered a bug in + * {@code StringUtils}.

+ * + *

Methods in this class give sample code to explain their operation. + * The symbol {@code *} is used to indicate any input including {@code null}.

+ * + *

#ThreadSafe#

+ * @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; + + /** + *

The maximum size to which the padding constant(s) can expand.

+ */ + 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+"); + + /** + *

{@code StringUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code StringUtils.trim(" foo ");}.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public StringUtils() { + super(); + } + + // Empty checks + //----------------------------------------------------------------------- + /** + *

Checks if a CharSequence is empty ("") or null.

+ * + *
+     * StringUtils.isEmpty(null)      = true
+     * StringUtils.isEmpty("")        = true
+     * StringUtils.isEmpty(" ")       = false
+     * StringUtils.isEmpty("bob")     = false
+     * StringUtils.isEmpty("  bob  ") = false
+     * 
+ * + *

NOTE: This method changed in Lang version 2.0. + * It no longer trims the CharSequence. + * That functionality is available in isBlank().

+ * + * @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; + } + + /** + *

Checks if a CharSequence is not empty ("") and not null.

+ * + *
+     * StringUtils.isNotEmpty(null)      = false
+     * StringUtils.isNotEmpty("")        = false
+     * StringUtils.isNotEmpty(" ")       = true
+     * StringUtils.isNotEmpty("bob")     = true
+     * StringUtils.isNotEmpty("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is not empty and not null + * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) + */ + public static boolean isNotEmpty(CharSequence cs) { + return !StringUtils.isEmpty(cs); + } + + /** + *

Checks if a CharSequence is whitespace, empty ("") or null.

+ * + *
+     * StringUtils.isBlank(null)      = true
+     * StringUtils.isBlank("")        = true
+     * StringUtils.isBlank(" ")       = true
+     * StringUtils.isBlank("bob")     = false
+     * StringUtils.isBlank("  bob  ") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace + * @since 2.0 + * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) + */ + public static boolean isBlank(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; + } + + /** + *

Checks if a CharSequence is not empty (""), not null and not whitespace only.

+ * + *
+     * StringUtils.isNotBlank(null)      = false
+     * StringUtils.isNotBlank("")        = false
+     * StringUtils.isNotBlank(" ")       = false
+     * StringUtils.isNotBlank("bob")     = true
+     * StringUtils.isNotBlank("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace + * @since 2.0 + * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) + */ + public static boolean isNotBlank(CharSequence cs) { + return !StringUtils.isBlank(cs); + } + + // Trim + //----------------------------------------------------------------------- + /** + *

Removes control characters (char <= 32) from both + * ends of this String, handling {@code null} by returning + * {@code null}.

+ * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #strip(String)}.

+ * + *

To trim your choice of characters, use the + * {@link #strip(String, String)} methods.

+ * + *
+     * StringUtils.trim(null)          = null
+     * StringUtils.trim("")            = ""
+     * StringUtils.trim("     ")       = ""
+     * StringUtils.trim("abc")         = "abc"
+     * StringUtils.trim("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed string, {@code null} if null String input + */ + public static String trim(String str) { + return str == null ? null : str.trim(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning {@code null} if the String is + * empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToNull(String)}.

+ * + *
+     * StringUtils.trimToNull(null)          = null
+     * StringUtils.trimToNull("")            = null
+     * StringUtils.trimToNull("     ")       = null
+     * StringUtils.trimToNull("abc")         = "abc"
+     * StringUtils.trimToNull("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, + * {@code null} if only chars <= 32, empty or null String input + * @since 2.0 + */ + public static String trimToNull(String str) { + String ts = trim(str); + return isEmpty(ts) ? null : ts; + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning an empty String ("") if the String + * is empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToEmpty(String)}.

+ * + *
+     * StringUtils.trimToEmpty(null)          = ""
+     * StringUtils.trimToEmpty("")            = ""
+     * StringUtils.trimToEmpty("     ")       = ""
+     * StringUtils.trimToEmpty("abc")         = "abc"
+     * StringUtils.trimToEmpty("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String trimToEmpty(String str) { + return str == null ? EMPTY : str.trim(); + } + + // Stripping + //----------------------------------------------------------------------- + /** + *

Strips whitespace from the start and end of a String.

+ * + *

This is similar to {@link #trim(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.strip(null)     = null
+     * StringUtils.strip("")       = ""
+     * StringUtils.strip("   ")    = ""
+     * StringUtils.strip("abc")    = "abc"
+     * StringUtils.strip("  abc")  = "abc"
+     * StringUtils.strip("abc  ")  = "abc"
+     * StringUtils.strip(" abc ")  = "abc"
+     * StringUtils.strip(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to remove whitespace from, may be null + * @return the stripped String, {@code null} if null String input + */ + public static String strip(String str) { + return strip(str, null); + } + + /** + *

Strips whitespace from the start and end of a String returning + * {@code null} if the String is empty ("") after the strip.

+ * + *

This is similar to {@link #trimToNull(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToNull(null)     = null
+     * StringUtils.stripToNull("")       = null
+     * StringUtils.stripToNull("   ")    = null
+     * StringUtils.stripToNull("abc")    = "abc"
+     * StringUtils.stripToNull("  abc")  = "abc"
+     * StringUtils.stripToNull("abc  ")  = "abc"
+     * StringUtils.stripToNull(" abc ")  = "abc"
+     * StringUtils.stripToNull(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the stripped String, + * {@code null} if whitespace, empty or null String input + * @since 2.0 + */ + public static String stripToNull(String str) { + if (str == null) { + return null; + } + str = strip(str, null); + return str.length() == 0 ? null : str; + } + + /** + *

Strips whitespace from the start and end of a String returning + * an empty String if {@code null} input.

+ * + *

This is similar to {@link #trimToEmpty(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToEmpty(null)     = ""
+     * StringUtils.stripToEmpty("")       = ""
+     * StringUtils.stripToEmpty("   ")    = ""
+     * StringUtils.stripToEmpty("abc")    = "abc"
+     * StringUtils.stripToEmpty("  abc")  = "abc"
+     * StringUtils.stripToEmpty("abc  ")  = "abc"
+     * StringUtils.stripToEmpty(" abc ")  = "abc"
+     * StringUtils.stripToEmpty(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the trimmed String, or an empty String if {@code null} input + * @since 2.0 + */ + public static String stripToEmpty(String str) { + return str == null ? EMPTY : strip(str, null); + } + + /** + *

Strips any of a set of characters from the start and end of a String. + * This is similar to {@link String#trim()} but allows the characters + * to be stripped to be controlled.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}. + * Alternatively use {@link #strip(String)}.

+ * + *
+     * StringUtils.strip(null, *)          = null
+     * StringUtils.strip("", *)            = ""
+     * StringUtils.strip("abc", null)      = "abc"
+     * StringUtils.strip("  abc", null)    = "abc"
+     * StringUtils.strip("abc  ", null)    = "abc"
+     * StringUtils.strip(" abc ", null)    = "abc"
+     * StringUtils.strip("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String strip(String str, String stripChars) { + if (isEmpty(str)) { + return str; + } + str = stripStart(str, stripChars); + return stripEnd(str, stripChars); + } + + /** + *

Strips any of a set of characters from the start of a String.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripStart(null, *)          = null
+     * StringUtils.stripStart("", *)            = ""
+     * StringUtils.stripStart("abc", "")        = "abc"
+     * StringUtils.stripStart("abc", null)      = "abc"
+     * StringUtils.stripStart("  abc", null)    = "abc"
+     * StringUtils.stripStart("abc  ", null)    = "abc  "
+     * StringUtils.stripStart(" abc ", null)    = "abc "
+     * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripStart(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); + } + + /** + *

Strips any of a set of characters from the end of a String.

+ * + *

A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripEnd(null, *)          = null
+     * StringUtils.stripEnd("", *)            = ""
+     * StringUtils.stripEnd("abc", "")        = "abc"
+     * StringUtils.stripEnd("abc", null)      = "abc"
+     * StringUtils.stripEnd("  abc", null)    = "  abc"
+     * StringUtils.stripEnd("abc  ", null)    = "abc"
+     * StringUtils.stripEnd(" abc ", null)    = " abc"
+     * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
+     * StringUtils.stripEnd("120.00", ".0")   = "12"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the set of characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripEnd(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 + //----------------------------------------------------------------------- + /** + *

Strips whitespace from the start and end of every String in an array. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored.

+ * + *
+     * StringUtils.stripAll(null)             = null
+     * StringUtils.stripAll([])               = []
+     * StringUtils.stripAll(["abc", "  abc"]) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null])  = ["abc", null]
+     * 
+ * + * @param strs the array to remove whitespace from, may be null + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(String... strs) { + return stripAll(strs, null); + } + + /** + *

Strips any of a set of characters from the start and end of every + * String in an array.

+ * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A new array is returned each time, except for length zero. + * A {@code null} array will return {@code null}. + * An empty array will return itself. + * A {@code null} array entry will be ignored. + * A {@code null} stripChars will strip whitespace as defined by + * {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripAll(null, *)                = null
+     * StringUtils.stripAll([], *)                  = []
+     * StringUtils.stripAll(["abc", "  abc"], null) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null], null)  = ["abc", null]
+     * StringUtils.stripAll(["abc  ", null], "yz")  = ["abc  ", null]
+     * StringUtils.stripAll(["yabcz", null], "yz")  = ["abc", null]
+     * 
+ * + * @param strs the array to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped Strings, {@code null} if null array input + */ + public static String[] stripAll(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; + } + + /** + *

Removes diacritics (~= accents) from a string. The case will not be altered.

+ *

For instance, 'à' will be replaced by 'a'.

+ *

Note that ligatures will be left as is.

+ * + *

This method will use the first available implementation of: + * Java 6's {@link java.text.Normalizer}, Java 1.3–1.5's {@code sun.text.Normalizer}

+ * + *
+     * StringUtils.stripAccents(null)                = null
+     * StringUtils.stripAccents("")                  = ""
+     * StringUtils.stripAccents("control")           = "control"
+     * StringUtils.stripAccents("éclair")     = "eclair"
+     * 
+ * + * @param input String to be stripped + * @return input text with diacritics removed + * + * @since 3.0 + */ + // See also Lucene's ASCIIFoldingFilter (Lucene 2.9) that replaces accented characters by their unaccented equivalent (and uncommitted bug fix: https://issues.apache.org/jira/browse/LUCENE-1343?focusedCommentId=12858907&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12858907). + public static String stripAccents(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 + //----------------------------------------------------------------------- + /** + *

Compares two CharSequences, returning {@code true} if they are equal.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.equals(null, null)   = true
+     * StringUtils.equals(null, "abc")  = false
+     * StringUtils.equals("abc", null)  = false
+     * StringUtils.equals("abc", "abc") = true
+     * StringUtils.equals("abc", "ABC") = false
+     * 
+ * + * @see 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); + } + + /** + *

Compares two CharSequences, returning {@code true} if they are equal ignoring + * the case.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered equal. Comparison is case insensitive.

+ * + *
+     * StringUtils.equalsIgnoreCase(null, null)   = true
+     * StringUtils.equalsIgnoreCase(null, "abc")  = false
+     * StringUtils.equalsIgnoreCase("abc", null)  = false
+     * StringUtils.equalsIgnoreCase("abc", "abc") = true
+     * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+     * 
+ * + * @param str1 the first CharSequence, may be null + * @param str2 the second CharSequence, may be null + * @return {@code true} if the CharSequence are equal, case insensitive, or + * both {@code null} + * @since 3.0 Changed signature from equalsIgnoreCase(String, String) to equalsIgnoreCase(CharSequence, CharSequence) + */ + public static boolean equalsIgnoreCase(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 + //----------------------------------------------------------------------- + /** + *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(int, int)} if possible.

+ * + *

A {@code null} or empty ("") CharSequence will return {@code INDEX_NOT_FOUND (-1)}.

+ * + *
+     * StringUtils.indexOf(null, *)         = -1
+     * StringUtils.indexOf("", *)           = -1
+     * StringUtils.indexOf("aabaabaa", 'a') = 0
+     * StringUtils.indexOf("aabaabaa", 'b') = 2
+     * 
+ * + * @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); + } + + /** + *

Finds the first index within a CharSequence from a start position, + * handling {@code null}. + * This method uses {@link String#indexOf(int, int)} if possible.

+ * + *

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}.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String, int)} if possible.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String, int)} if possible.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Finds the n-th index within a CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + *

Note that 'head(CharSequence str, int n)' may be implemented as:

+ * + *
+     *   str.substring(0, lastOrdinalIndexOf(str, "\n", n))
+     * 
+ * + * @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); + } + + /** + *

Finds the n-th index within a String, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + * @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; + } + + /** + *

Case in-sensitive find of the first index within a CharSequence.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Case in-sensitive find of the first index within a CharSequence + * from the specified position.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(int)} if possible.

+ * + *

A {@code null} or empty ("") CharSequence will return {@code -1}.

+ * + *
+     * StringUtils.lastIndexOf(null, *)         = -1
+     * StringUtils.lastIndexOf("", *)           = -1
+     * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
+     * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
+     * 
+ * + * @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()); + } + + /** + *

Finds the last index within a CharSequence from a start position, + * handling {@code null}. + * This method uses {@link String#lastIndexOf(int, int)} if possible.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String)} if possible.

+ * + *

A {@code null} CharSequence will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + * @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()); + } + + /** + *

Finds the n-th last index within a String, handling {@code null}. + * This method uses {@link String#lastIndexOf(String)}.

+ * + *

A {@code null} String will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + *

Note that 'tail(CharSequence str, int n)' may be implemented as:

+ * + *
+     *   str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
+     * 
+ * + * @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); + } + + /** + *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String, int)} if possible.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Case in-sensitive find of the last index within a CharSequence.

+ * + *

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.

+ * + *
+     * StringUtils.lastIndexOfIgnoreCase(null, *)          = -1
+     * StringUtils.lastIndexOfIgnoreCase(*, null)          = -1
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A")  = 7
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B")  = 5
+     * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
+     * 
+ * + * @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()); + } + + /** + *

Case in-sensitive find of the last index within a CharSequence + * from the specified position.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Checks if CharSequence contains a search character, handling {@code null}. + * This method uses {@link String#indexOf(int)} if possible.

+ * + *

A {@code null} or empty ("") CharSequence will return {@code false}.

+ * + *
+     * StringUtils.contains(null, *)    = false
+     * StringUtils.contains("", *)      = false
+     * StringUtils.contains("abc", 'a') = true
+     * StringUtils.contains("abc", 'z') = false
+     * 
+ * + * @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; + } + + /** + *

Checks if CharSequence contains a search CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

+ * + *

A {@code null} CharSequence will return {@code false}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Checks if CharSequence contains a search CharSequence irrespective of case, + * handling {@code null}. Case-insensitivity is defined as by + * {@link String#equalsIgnoreCase(String)}. + * + *

A {@code null} CharSequence will return {@code false}.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Search a CharSequence to find the first index of any + * character in the given set of characters.

+ * + *

A {@code null} String will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Search a CharSequence to find the first index of any + * character in the given set of characters.

+ * + *

A {@code null} String will return {@code -1}. + * A {@code null} search string will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Checks if the CharSequence contains any character in the given + * set of characters.

+ * + *

A {@code null} CharSequence will return {@code false}. + * A {@code null} or zero length search array will return {@code false}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

+ * Checks if the CharSequence contains any character in the given set of characters. + *

+ * + *

+ * A {@code null} CharSequence will return {@code false}. A {@code null} search CharSequence will return + * {@code false}. + *

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Searches a CharSequence to find the first index of any + * character not in the given set of characters.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} or zero length search array will return {@code -1}.

+ * + *
+     * 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
+
+     * 
+ * + * @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; + } + + /** + *

Search a CharSequence to find the first index of any + * character not in the given set of characters.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A {@code null} or empty search string will return {@code -1}.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Checks if the CharSequence contains only certain characters.

+ * + *

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}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Checks if the CharSequence contains only certain characters.

+ * + *

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}.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Checks that the CharSequence does not contain certain characters.

+ * + *

A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty CharSequence (length()=0) always returns true.

+ * + *
+     * StringUtils.containsNone(null, *)       = true
+     * StringUtils.containsNone(*, null)       = true
+     * StringUtils.containsNone("", *)         = true
+     * StringUtils.containsNone("ab", '')      = true
+     * StringUtils.containsNone("abab", 'xyz') = true
+     * StringUtils.containsNone("ab1", 'xyz')  = true
+     * StringUtils.containsNone("abz", 'xyz')  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param searchChars an array of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, char[]) to containsNone(CharSequence, char...) + */ + public static boolean containsNone(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; + } + + /** + *

Checks that the CharSequence does not contain certain characters.

+ * + *

A {@code null} CharSequence will return {@code true}. + * A {@code null} invalid character array will return {@code true}. + * An empty String ("") always returns true.

+ * + *
+     * StringUtils.containsNone(null, *)       = true
+     * StringUtils.containsNone(*, null)       = true
+     * StringUtils.containsNone("", *)         = true
+     * StringUtils.containsNone("ab", "")      = true
+     * StringUtils.containsNone("abab", "xyz") = true
+     * StringUtils.containsNone("ab1", "xyz")  = true
+     * StringUtils.containsNone("abz", "xyz")  = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @param invalidChars a String of invalid chars, may be null + * @return true if it contains none of the invalid chars, or is null + * @since 2.0 + * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String) + */ + public static boolean containsNone(CharSequence cs, String invalidChars) { + if (cs == null || invalidChars == null) { + return true; + } + return containsNone(cs, invalidChars.toCharArray()); + } + + // IndexOfAny strings + //----------------------------------------------------------------------- + /** + *

Find the first index of any of a set of potential substrings.

+ * + *

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.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Find the latest index of any of a set of potential substrings.

+ * + *

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

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Gets a substring from the specified String avoiding exceptions.

+ * + *

A negative start position can be used to start {@code n} + * characters from the end of the String.

+ * + *

A {@code null} String will return {@code null}. + * An empty ("") String will return "".

+ * + *
+     * StringUtils.substring(null, *)   = null
+     * StringUtils.substring("", *)     = ""
+     * StringUtils.substring("abc", 0)  = "abc"
+     * StringUtils.substring("abc", 2)  = "c"
+     * StringUtils.substring("abc", 4)  = ""
+     * StringUtils.substring("abc", -2) = "bc"
+     * StringUtils.substring("abc", -4) = "abc"
+     * 
+ * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @return substring from start position, {@code null} if null String input + */ + public static String substring(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); + } + + /** + *

Gets a substring from the specified String avoiding exceptions.

+ * + *

A negative start position can be used to start/end {@code n} + * characters from the end of the String.

+ * + *

The returned substring starts with the character in the {@code start} + * position and ends before the {@code end} position. All position counting is + * zero-based -- i.e., to start at the beginning of the string use + * {@code start = 0}. Negative start and end positions can be used to + * specify offsets relative to the end of the String.

+ * + *

If {@code start} is not strictly to the left of {@code end}, "" + * is returned.

+ * + *
+     * StringUtils.substring(null, *, *)    = null
+     * StringUtils.substring("", * ,  *)    = "";
+     * StringUtils.substring("abc", 0, 2)   = "ab"
+     * StringUtils.substring("abc", 2, 0)   = ""
+     * StringUtils.substring("abc", 2, 4)   = "c"
+     * StringUtils.substring("abc", 4, 6)   = ""
+     * StringUtils.substring("abc", 2, 2)   = ""
+     * StringUtils.substring("abc", -2, -1) = "b"
+     * StringUtils.substring("abc", -4, 2)  = "ab"
+     * 
+ * + * @param str the String to get the substring from, may be null + * @param start the position to start from, negative means + * count back from the end of the String by this many characters + * @param end the position to end at (exclusive), negative means + * count back from the end of the String by this many characters + * @return substring from start position to end position, + * {@code null} if null String input + */ + public static String substring(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 + //----------------------------------------------------------------------- + /** + *

Gets the leftmost {@code len} characters of a String.

+ * + *

If {@code len} characters are not available, or the + * String is {@code null}, the String will be returned without + * an exception. An empty String is returned if len is negative.

+ * + *
+     * StringUtils.left(null, *)    = null
+     * StringUtils.left(*, -ve)     = ""
+     * StringUtils.left("", *)      = ""
+     * StringUtils.left("abc", 0)   = ""
+     * StringUtils.left("abc", 2)   = "ab"
+     * StringUtils.left("abc", 4)   = "abc"
+     * 
+ * + * @param str the String to get the leftmost characters from, may be null + * @param len the length of the required String + * @return the leftmost characters, {@code null} if null String input + */ + public static String left(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); + } + + /** + *

Gets the rightmost {@code len} characters of a String.

+ * + *

If {@code len} characters are not available, or the String + * is {@code null}, the String will be returned without an + * an exception. An empty String is returned if len is negative.

+ * + *
+     * StringUtils.right(null, *)    = null
+     * StringUtils.right(*, -ve)     = ""
+     * StringUtils.right("", *)      = ""
+     * StringUtils.right("abc", 0)   = ""
+     * StringUtils.right("abc", 2)   = "bc"
+     * StringUtils.right("abc", 4)   = "abc"
+     * 
+ * + * @param str the String to get the rightmost characters from, may be null + * @param len the length of the required String + * @return the rightmost characters, {@code null} if null String input + */ + public static String right(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); + } + + /** + *

Gets {@code len} characters from the middle of a String.

+ * + *

If {@code len} characters are not available, the remainder + * of the String will be returned without an exception. If the + * String is {@code null}, {@code null} will be returned. + * An empty String is returned if len is negative or exceeds the + * length of {@code str}.

+ * + *
+     * StringUtils.mid(null, *, *)    = null
+     * StringUtils.mid(*, *, -ve)     = ""
+     * StringUtils.mid("", 0, *)      = ""
+     * StringUtils.mid("abc", 0, 2)   = "ab"
+     * StringUtils.mid("abc", 0, 4)   = "abc"
+     * StringUtils.mid("abc", 2, 4)   = "c"
+     * StringUtils.mid("abc", 4, 2)   = ""
+     * StringUtils.mid("abc", -2, 2)  = "ab"
+     * 
+ * + * @param str the String to get the characters from, may be null + * @param pos the position to start from, negative treated as zero + * @param len the length of the required String + * @return the middle characters, {@code null} if null String input + */ + public static String mid(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 + //----------------------------------------------------------------------- + /** + *

Gets the substring before the first occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+     * StringUtils.substringBefore(null, *)      = null
+     * StringUtils.substringBefore("", *)        = ""
+     * StringUtils.substringBefore("abc", "a")   = ""
+     * StringUtils.substringBefore("abcba", "b") = "a"
+     * StringUtils.substringBefore("abc", "c")   = "ab"
+     * StringUtils.substringBefore("abc", "d")   = "abc"
+     * StringUtils.substringBefore("abc", "")    = ""
+     * StringUtils.substringBefore("abc", null)  = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBefore(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); + } + + /** + *

Gets the substring after the first occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the empty string if the + * input string is not {@code null}.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+     * StringUtils.substringAfter(null, *)      = null
+     * StringUtils.substringAfter("", *)        = ""
+     * StringUtils.substringAfter(*, null)      = ""
+     * StringUtils.substringAfter("abc", "a")   = "bc"
+     * StringUtils.substringAfter("abcba", "b") = "cba"
+     * StringUtils.substringAfter("abc", "c")   = ""
+     * StringUtils.substringAfter("abc", "d")   = ""
+     * StringUtils.substringAfter("abc", "")    = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the first occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfter(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()); + } + + /** + *

Gets the substring before the last occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+     * StringUtils.substringBeforeLast(null, *)      = null
+     * StringUtils.substringBeforeLast("", *)        = ""
+     * StringUtils.substringBeforeLast("abcba", "b") = "abc"
+     * StringUtils.substringBeforeLast("abc", "c")   = "ab"
+     * StringUtils.substringBeforeLast("a", "a")     = ""
+     * StringUtils.substringBeforeLast("a", "z")     = "a"
+     * StringUtils.substringBeforeLast("a", null)    = "a"
+     * StringUtils.substringBeforeLast("a", "")      = "a"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringBeforeLast(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); + } + + /** + *

Gets the substring after the last occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * An empty or {@code null} separator will return the empty string if + * the input string is not {@code null}.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+     * StringUtils.substringAfterLast(null, *)      = null
+     * StringUtils.substringAfterLast("", *)        = ""
+     * StringUtils.substringAfterLast(*, "")        = ""
+     * StringUtils.substringAfterLast(*, null)      = ""
+     * StringUtils.substringAfterLast("abc", "a")   = "bc"
+     * StringUtils.substringAfterLast("abcba", "b") = "a"
+     * StringUtils.substringAfterLast("abc", "c")   = ""
+     * StringUtils.substringAfterLast("a", "a")     = ""
+     * StringUtils.substringAfterLast("a", "z")     = ""
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the last occurrence of the separator, + * {@code null} if null String input + * @since 2.0 + */ + public static String substringAfterLast(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 + //----------------------------------------------------------------------- + /** + *

Gets the String that is nested in between two instances of the + * same String.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} tag returns {@code null}.

+ * + *
+     * StringUtils.substringBetween(null, *)            = null
+     * StringUtils.substringBetween("", "")             = ""
+     * StringUtils.substringBetween("", "tag")          = null
+     * StringUtils.substringBetween("tagabctag", null)  = null
+     * StringUtils.substringBetween("tagabctag", "")    = ""
+     * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+     * 
+ * + * @param str the String containing the substring, may be null + * @param tag the String before and after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(String str, String tag) { + return substringBetween(str, tag, tag); + } + + /** + *

Gets the String that is nested in between two Strings. + * Only the first match is returned.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} open/close returns {@code null} (no match). + * An empty ("") open and close returns an empty string.

+ * + *
+     * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
+     * StringUtils.substringBetween(null, *, *)          = null
+     * StringUtils.substringBetween(*, null, *)          = null
+     * StringUtils.substringBetween(*, *, null)          = null
+     * StringUtils.substringBetween("", "", "")          = ""
+     * StringUtils.substringBetween("", "", "]")         = null
+     * StringUtils.substringBetween("", "[", "]")        = null
+     * StringUtils.substringBetween("yabcz", "", "")     = ""
+     * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
+     * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
+     * 
+ * + * @param str the String containing the substring, may be null + * @param open the String before the substring, may be null + * @param close the String after the substring, may be null + * @return the substring, {@code null} if no match + * @since 2.0 + */ + public static String substringBetween(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; + } + + /** + *

Searches a String for substrings delimited by a start and end tag, + * returning all matching substrings in an array.

+ * + *

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).

+ * + *
+     * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
+     * StringUtils.substringsBetween(null, *, *)            = null
+     * StringUtils.substringsBetween(*, null, *)            = null
+     * StringUtils.substringsBetween(*, *, null)            = null
+     * StringUtils.substringsBetween("", "[", "]")          = []
+     * 
+ * + * @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 list = new ArrayList(); + 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 + //----------------------------------------------------------------------- + /** + *

Splits the provided text into an array, using whitespace as the + * separator. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.split(null)       = null
+     * StringUtils.split("")         = []
+     * StringUtils.split("abc def")  = ["abc", "def"]
+     * StringUtils.split("abc  def") = ["abc", "def"]
+     * StringUtils.split(" abc ")    = ["abc"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array, separator specified. + * This is an alternative to using StringTokenizer.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * 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"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array, separators specified. + * This is an alternative to using StringTokenizer.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *
+     * 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"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array with a maximum length, + * separators specified.

+ * + *

The separator is not included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *

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).

+ * + *
+     * 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"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array, separator string specified.

+ * + *

The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * 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"]
+     * 
+ * + * @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 ) ; + } + + /** + *

Splits the provided text into an array, separator string specified. + * Returns a maximum of {@code max} substrings.

+ * + *

The separator(s) will not be included in the returned String array. + * Adjacent separators are treated as one separator.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * 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"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array, separator string specified.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * 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"]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array, separator string specified. + * Returns a maximum of {@code max} substrings.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separator splits on whitespace.

+ * + *
+     * 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"]
+     * 
+ * + * @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 substrings = new ArrayList(); + 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()]); + } + + // ----------------------------------------------------------------------- + /** + *

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)}.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.splitPreserveAllTokens(null)       = null
+     * StringUtils.splitPreserveAllTokens("")         = []
+     * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
+     * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
+     * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
+     * 
+ * + * @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); + } + + /** + *

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.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * 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", ""]
+     * 
+ * + * @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 list = new ArrayList(); + 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()]); + } + + /** + *

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.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *
+     * 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", ""]
+     * 
+ * + * @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); + } + + /** + *

Splits the provided text into an array with a maximum length, + * separators specified, preserving all tokens, including empty tokens + * created by adjacent separators.

+ * + *

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.

+ * + *

A {@code null} input String returns {@code null}. + * A {@code null} separatorChars splits on whitespace.

+ * + *

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).

+ * + *
+     * 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"]
+     * 
+ * + * @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 list = new ArrayList(); + 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()]); + } + + /** + *

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. + *

+     * 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"]
+     * 
+ * @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); + } + + /** + *

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. + *

+     * 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"]
+     * 
+ * @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); + } + + /** + *

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 list = new ArrayList(); + 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 + //----------------------------------------------------------------------- + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No separator is added to the joined String. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null)            = null
+     * StringUtils.join([])              = ""
+     * StringUtils.join([null])          = ""
+     * StringUtils.join(["a", "b", "c"]) = "abc"
+     * StringUtils.join([null, "", "a"]) = "a"
+     * 
+ * + * @param 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 String join(T... elements) { + return join(elements, null); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * 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"
+     * 
+ * + * @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); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * 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"
+     * 
+ * + * @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(); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)                = null
+     * StringUtils.join([], *)                  = ""
+     * StringUtils.join([null], *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
+     * StringUtils.join(["a", "b", "c"], null)  = "abc"
+     * StringUtils.join(["a", "b", "c"], "")    = "abc"
+     * StringUtils.join([null, "", "a"], ',')   = ",,a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @return the joined String, {@code null} if null array input + */ + public static String join(Object[] array, String separator) { + if (array == null) { + return null; + } + return join(array, separator, 0, array.length); + } + + /** + *

Joins the elements of the provided array into a single String + * containing the provided list of elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String (""). + * Null objects or empty strings within the array are represented by + * empty strings.

+ * + *
+     * StringUtils.join(null, *)                = null
+     * StringUtils.join([], *)                  = ""
+     * StringUtils.join([null], *)              = ""
+     * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
+     * StringUtils.join(["a", "b", "c"], null)  = "abc"
+     * StringUtils.join(["a", "b", "c"], "")    = "abc"
+     * StringUtils.join([null, "", "a"], ',')   = ",,a"
+     * 
+ * + * @param array the array of values to join together, may be null + * @param separator the separator character to use, null treated as "" + * @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(); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @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(); + } + + /** + *

Joins the elements of the provided {@code Iterator} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @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(); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. Null objects or empty + * strings within the iteration are represented by empty strings.

+ * + *

See the examples here: {@link #join(Object[],char)}.

+ * + * @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); + } + + /** + *

Joins the elements of the provided {@code Iterable} into + * a single String containing the provided elements.

+ * + *

No delimiter is added before or after the list. + * A {@code null} separator is the same as an empty String ("").

+ * + *

See the examples here: {@link #join(Object[],String)}.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Deletes all whitespaces from a String as defined by + * {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.deleteWhitespace(null)         = null
+     * StringUtils.deleteWhitespace("")           = ""
+     * StringUtils.deleteWhitespace("abc")        = "abc"
+     * StringUtils.deleteWhitespace("   ab  c  ") = "abc"
+     * 
+ * + * @param str the String to delete whitespace from, may be null + * @return the String without whitespaces, {@code null} if null String input + */ + public static String deleteWhitespace(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 + //----------------------------------------------------------------------- + /** + *

Removes a substring only if it is at the beginning of a source string, + * otherwise returns the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeStart(null, *)      = null
+     * StringUtils.removeStart("", *)        = ""
+     * StringUtils.removeStart(*, null)      = *
+     * StringUtils.removeStart("www.domain.com", "www.")   = "domain.com"
+     * StringUtils.removeStart("domain.com", "www.")       = "domain.com"
+     * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeStart("abc", "")    = "abc"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeStart(String str, String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + if (str.startsWith(remove)){ + return str.substring(remove.length()); + } + return str; + } + + /** + *

Case insensitive removal of a substring if it is at the beginning of a source string, + * otherwise returns the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * 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"
+     * 
+ * + * @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; + } + + /** + *

Removes a substring only if it is at the end of a source string, + * otherwise returns the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * StringUtils.removeEnd(null, *)      = null
+     * StringUtils.removeEnd("", *)        = ""
+     * StringUtils.removeEnd(*, null)      = *
+     * StringUtils.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
+     * StringUtils.removeEnd("www.domain.com", ".com")   = "www.domain"
+     * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
+     * StringUtils.removeEnd("abc", "")    = "abc"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String removeEnd(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; + } + + /** + *

Case insensitive removal of a substring if it is at the end of a source string, + * otherwise returns the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} search string will return the source string.

+ * + *
+     * 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")
+     * 
+ * + * @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; + } + + /** + *

Removes all occurrences of a substring from within the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string. + * A {@code null} remove string will return the source string. + * An empty ("") remove string will return the source string.

+ * + *
+     * StringUtils.remove(null, *)        = null
+     * StringUtils.remove("", *)          = ""
+     * StringUtils.remove(*, null)        = *
+     * StringUtils.remove(*, "")          = *
+     * StringUtils.remove("queued", "ue") = "qd"
+     * StringUtils.remove("queued", "zz") = "queued"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the String to search for and remove, may be null + * @return the substring with the string removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(String str, String remove) { + if (isEmpty(str) || isEmpty(remove)) { + return str; + } + return replace(str, remove, EMPTY, -1); + } + + /** + *

Removes all occurrences of a character from within the source string.

+ * + *

A {@code null} source string will return {@code null}. + * An empty ("") source string will return the empty string.

+ * + *
+     * StringUtils.remove(null, *)       = null
+     * StringUtils.remove("", *)         = ""
+     * StringUtils.remove("queued", 'u') = "qeed"
+     * StringUtils.remove("queued", 'z') = "queued"
+     * 
+ * + * @param str the source String to search, may be null + * @param remove the char to search for and remove, may be null + * @return the substring with the char removed if found, + * {@code null} if null String input + * @since 2.1 + */ + public static String remove(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 + //----------------------------------------------------------------------- + /** + *

Replaces a String with another String inside a larger String, once.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replaceOnce(null, *, *)        = null
+     * StringUtils.replaceOnce("", *, *)          = ""
+     * StringUtils.replaceOnce("any", null, *)    = "any"
+     * StringUtils.replaceOnce("any", *, null)    = "any"
+     * StringUtils.replaceOnce("any", "", *)      = "any"
+     * StringUtils.replaceOnce("aba", "a", null)  = "aba"
+     * StringUtils.replaceOnce("aba", "a", "")    = "ba"
+     * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
+     * 
+ * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replaceOnce(String text, String searchString, String replacement) { + return replace(text, searchString, replacement, 1); + } + + /** + *

Replaces all occurrences of a String within another String.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replace(null, *, *)        = null
+     * StringUtils.replace("", *, *)          = ""
+     * StringUtils.replace("any", null, *)    = "any"
+     * StringUtils.replace("any", *, null)    = "any"
+     * StringUtils.replace("any", "", *)      = "any"
+     * StringUtils.replace("aba", "a", null)  = "aba"
+     * StringUtils.replace("aba", "a", "")    = "b"
+     * StringUtils.replace("aba", "a", "z")   = "zbz"
+     * 
+ * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(String text, String searchString, String replacement) { + return replace(text, searchString, replacement, -1); + } + + /** + *

Replaces a String with another String inside a larger String, + * for the first {@code max} values of the search String.

+ * + *

A {@code null} reference passed to this method is a no-op.

+ * + *
+     * StringUtils.replace(null, *, *, *)         = null
+     * StringUtils.replace("", *, *, *)           = ""
+     * StringUtils.replace("any", null, *, *)     = "any"
+     * StringUtils.replace("any", *, null, *)     = "any"
+     * StringUtils.replace("any", "", *, *)       = "any"
+     * StringUtils.replace("any", *, *, 0)        = "any"
+     * StringUtils.replace("abaa", "a", null, -1) = "abaa"
+     * StringUtils.replace("abaa", "a", "", -1)   = "b"
+     * StringUtils.replace("abaa", "a", "z", 0)   = "abaa"
+     * StringUtils.replace("abaa", "a", "z", 1)   = "zbaa"
+     * StringUtils.replace("abaa", "a", "z", 2)   = "zbza"
+     * StringUtils.replace("abaa", "a", "z", -1)  = "zbzz"
+     * 
+ * + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace it with, may be null + * @param max maximum number of values to replace, or {@code -1} if no maximum + * @return the text with any replacements processed, + * {@code null} if null String input + */ + public static String replace(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(); + } + + /** + *

+ * Replaces all occurrences of Strings within another String. + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. This will not repeat. For repeating replaces, call the + * overloaded method. + *

+ * + *
+     *  StringUtils.replaceEach(null, *, *)        = null
+     *  StringUtils.replaceEach("", *, *)          = ""
+     *  StringUtils.replaceEach("aba", null, null) = "aba"
+     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
+     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
+     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
+     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
+     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
+     *  (example of how it does not repeat)
+     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
+     * 
+ * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + * @since 2.4 + */ + public static String replaceEach(String text, String[] searchList, String[] replacementList) { + return replaceEach(text, searchList, replacementList, false, 0); + } + + /** + *

+ * Replaces all occurrences of Strings within another String. + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

+ * + *
+     *  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"
+     * 
+ * + * @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); + } + + /** + *

+ * Replaces all occurrences of Strings within another String. + *

+ * + *

+ * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

+ * + *
+     *  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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Replaces all occurrences of a character in a String with another. + * This is a null-safe version of {@link String#replace(char, char)}.

+ * + *

A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string.

+ * + *
+     * StringUtils.replaceChars(null, *, *)        = null
+     * StringUtils.replaceChars("", *, *)          = ""
+     * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+     * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+     * 
+ * + * @param str String to replace characters in, may be null + * @param searchChar the character to search for, may be null + * @param replaceChar the character to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(String str, char searchChar, char replaceChar) { + if (str == null) { + return null; + } + return str.replace(searchChar, replaceChar); + } + + /** + *

Replaces multiple characters in a String in one go. + * This method can also be used to delete characters.

+ * + *

For example:
+ * replaceChars("hello", "ho", "jy") = jelly.

+ * + *

A {@code null} string input returns {@code null}. + * An empty ("") string input returns an empty string. + * A null or empty set of search characters returns the input string.

+ * + *

The length of the search characters should normally equal the length + * of the replace characters. + * If the search characters is longer, then the extra search characters + * are deleted. + * If the search characters is shorter, then the extra replace characters + * are ignored.

+ * + *
+     * StringUtils.replaceChars(null, *, *)           = null
+     * StringUtils.replaceChars("", *, *)             = ""
+     * StringUtils.replaceChars("abc", null, *)       = "abc"
+     * StringUtils.replaceChars("abc", "", *)         = "abc"
+     * StringUtils.replaceChars("abc", "b", null)     = "ac"
+     * StringUtils.replaceChars("abc", "b", "")       = "ac"
+     * StringUtils.replaceChars("abcba", "bc", "yz")  = "ayzya"
+     * StringUtils.replaceChars("abcba", "bc", "y")   = "ayya"
+     * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
+     * 
+ * + * @param str String to replace characters in, may be null + * @param searchChars a set of characters to search for, may be null + * @param replaceChars a set of characters to replace, may be null + * @return modified String, {@code null} if null string input + * @since 2.0 + */ + public static String replaceChars(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 + //----------------------------------------------------------------------- + /** + *

Overlays part of a String with another String.

+ * + *

A {@code null} string input returns {@code null}. + * A negative index is treated as zero. + * An index greater than the string length is treated as the string length. + * The start index is always the smaller of the two indices.

+ * + *
+     * StringUtils.overlay(null, *, *, *)            = null
+     * StringUtils.overlay("", "abc", 0, 0)          = "abc"
+     * StringUtils.overlay("abcdef", null, 2, 4)     = "abef"
+     * StringUtils.overlay("abcdef", "", 2, 4)       = "abef"
+     * StringUtils.overlay("abcdef", "", 4, 2)       = "abef"
+     * StringUtils.overlay("abcdef", "zzzz", 2, 4)   = "abzzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", 4, 2)   = "abzzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", -1, 4)  = "zzzzef"
+     * StringUtils.overlay("abcdef", "zzzz", 2, 8)   = "abzzzz"
+     * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
+     * StringUtils.overlay("abcdef", "zzzz", 8, 10)  = "abcdefzzzz"
+     * 
+ * + * @param str the String to do overlaying in, may be null + * @param overlay the String to overlay, may be null + * @param start the position to start overlaying at + * @param end the position to stop overlaying before + * @return overlayed String, {@code null} if null String input + * @since 2.0 + */ + public static String overlay(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 + //----------------------------------------------------------------------- + /** + *

Removes one newline from end of a String if it's there, + * otherwise leave it alone. A newline is "{@code \n}", + * "{@code \r}", or "{@code \r\n}".

+ * + *

NOTE: This method changed in 2.0. + * It now more closely matches Perl chomp.

+ * + *
+     * 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")        = ""
+     * 
+ * + * @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); + } + + /** + *

Removes {@code separator} from the end of + * {@code str} if it's there, otherwise leave it alone.

+ * + *

NOTE: This method changed in version 2.0. + * It now more closely matches Perl chomp. + * For the previous behavior, use {@link #substringBeforeLast(String, String)}. + * This method uses {@link String#endsWith(String)}.

+ * + *
+     * StringUtils.chomp(null, *)         = null
+     * StringUtils.chomp("", *)           = ""
+     * StringUtils.chomp("foobar", "bar") = "foo"
+     * StringUtils.chomp("foobar", "baz") = "foobar"
+     * StringUtils.chomp("foo", "foo")    = ""
+     * StringUtils.chomp("foo ", "foo")   = "foo "
+     * StringUtils.chomp(" foo", "foo")   = " "
+     * StringUtils.chomp("foo", "foooo")  = "foo"
+     * StringUtils.chomp("foo", "")       = "foo"
+     * StringUtils.chomp("foo", null)     = "foo"
+     * 
+ * + * @param str the String to chomp from, may be null + * @param separator separator String, may be null + * @return String without trailing separator, {@code null} if null String input + * @deprecated This feature will be removed in Lang 4.0 + */ + @Deprecated + public static String chomp(String str, String separator) { + return removeEnd(str,separator); + } + + // Chopping + //----------------------------------------------------------------------- + /** + *

Remove the last character from a String.

+ * + *

If the String ends in {@code \r\n}, then remove both + * of them.

+ * + *
+     * 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")        = ""
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Repeat a String {@code repeat} times to form a + * new String.

+ * + *
+     * StringUtils.repeat(null, 2) = null
+     * StringUtils.repeat("", 0)   = ""
+     * StringUtils.repeat("", 2)   = ""
+     * StringUtils.repeat("a", 3)  = "aaa"
+     * StringUtils.repeat("ab", 2) = "abab"
+     * StringUtils.repeat("a", -2) = ""
+     * 
+ * + * @param str the String to repeat, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + */ + public static String repeat(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(); + } + } + + /** + *

Repeat a String {@code repeat} times to form a + * new String, with a String separator injected each time.

+ * + *
+     * StringUtils.repeat(null, null, 2) = null
+     * StringUtils.repeat(null, "x", 2)  = null
+     * StringUtils.repeat("", null, 0)   = ""
+     * StringUtils.repeat("", "", 2)     = ""
+     * StringUtils.repeat("", "x", 3)    = "xxx"
+     * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
+     * 
+ * + * @param str the String to repeat, may be null + * @param separator the String to inject, may be null + * @param repeat number of times to repeat str, negative treated as zero + * @return a new String consisting of the original String repeated, + * {@code null} if null String input + * @since 2.5 + */ + public static String repeat(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); + } + } + + /** + *

Returns padding using the specified delimiter repeated + * to a given length.

+ * + *
+     * StringUtils.repeat(0, 'e')  = ""
+     * StringUtils.repeat(3, 'e')  = "eee"
+     * StringUtils.repeat(-2, 'e') = ""
+     * 
+ * + *

Note: this method doesn't not support padding with + * Unicode Supplementary Characters + * as they require a pair of {@code char}s to be represented. + * If you are needing to support full I18N of your applications + * consider using {@link #repeat(String, int)} instead. + *

+ * + * @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); + } + + /** + *

Right pad a String with spaces (' ').

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *)   = null
+     * StringUtils.rightPad("", 3)     = "   "
+     * StringUtils.rightPad("bat", 3)  = "bat"
+     * StringUtils.rightPad("bat", 5)  = "bat  "
+     * StringUtils.rightPad("bat", 1)  = "bat"
+     * StringUtils.rightPad("bat", -1) = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(String str, int size) { + return rightPad(str, size, ' '); + } + + /** + *

Right pad a String with a specified character.

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *, *)     = null
+     * StringUtils.rightPad("", 3, 'z')     = "zzz"
+     * StringUtils.rightPad("bat", 3, 'z')  = "bat"
+     * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
+     * StringUtils.rightPad("bat", 1, 'z')  = "bat"
+     * StringUtils.rightPad("bat", -1, 'z') = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String rightPad(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)); + } + + /** + *

Right pad a String with a specified String.

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.rightPad(null, *, *)      = null
+     * StringUtils.rightPad("", 3, "z")      = "zzz"
+     * StringUtils.rightPad("bat", 3, "yz")  = "bat"
+     * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
+     * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
+     * StringUtils.rightPad("bat", 1, "yz")  = "bat"
+     * StringUtils.rightPad("bat", -1, "yz") = "bat"
+     * StringUtils.rightPad("bat", 5, null)  = "bat  "
+     * StringUtils.rightPad("bat", 5, "")    = "bat  "
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return right padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String rightPad(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)); + } + } + + /** + *

Left pad a String with spaces (' ').

+ * + *

The String is padded to the size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *)   = null
+     * StringUtils.leftPad("", 3)     = "   "
+     * StringUtils.leftPad("bat", 3)  = "bat"
+     * StringUtils.leftPad("bat", 5)  = "  bat"
+     * StringUtils.leftPad("bat", 1)  = "bat"
+     * StringUtils.leftPad("bat", -1) = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(String str, int size) { + return leftPad(str, size, ' '); + } + + /** + *

Left pad a String with a specified character.

+ * + *

Pad to a size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *, *)     = null
+     * StringUtils.leftPad("", 3, 'z')     = "zzz"
+     * StringUtils.leftPad("bat", 3, 'z')  = "bat"
+     * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
+     * StringUtils.leftPad("bat", 1, 'z')  = "bat"
+     * StringUtils.leftPad("bat", -1, 'z') = "bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padChar the character to pad with + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + * @since 2.0 + */ + public static String leftPad(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); + } + + /** + *

Left pad a String with a specified String.

+ * + *

Pad to a size of {@code size}.

+ * + *
+     * StringUtils.leftPad(null, *, *)      = null
+     * StringUtils.leftPad("", 3, "z")      = "zzz"
+     * StringUtils.leftPad("bat", 3, "yz")  = "bat"
+     * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
+     * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
+     * StringUtils.leftPad("bat", 1, "yz")  = "bat"
+     * StringUtils.leftPad("bat", -1, "yz") = "bat"
+     * StringUtils.leftPad("bat", 5, null)  = "  bat"
+     * StringUtils.leftPad("bat", 5, "")    = "  bat"
+     * 
+ * + * @param str the String to pad out, may be null + * @param size the size to pad to + * @param padStr the String to pad with, null or empty treated as single space + * @return left padded String or original String if no padding is necessary, + * {@code null} if null String input + */ + public static String leftPad(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 + //----------------------------------------------------------------------- + /** + *

Centers a String in a larger String of size {@code size} + * using the space character (' ').

+ * + *

If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

+ * + *

Equivalent to {@code center(str, size, " ")}.

+ * + *
+     * StringUtils.center(null, *)   = null
+     * StringUtils.center("", 4)     = "    "
+     * StringUtils.center("ab", -1)  = "ab"
+     * StringUtils.center("ab", 4)   = " ab "
+     * StringUtils.center("abcd", 2) = "abcd"
+     * StringUtils.center("a", 4)    = " a  "
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @return centered String, {@code null} if null String input + */ + public static String center(String str, int size) { + return center(str, size, ' '); + } + + /** + *

Centers a String in a larger String of size {@code size}. + * Uses a supplied character as the value to pad the String with.

+ * + *

If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

+ * + *
+     * StringUtils.center(null, *, *)     = null
+     * StringUtils.center("", 4, ' ')     = "    "
+     * StringUtils.center("ab", -1, ' ')  = "ab"
+     * StringUtils.center("ab", 4, ' ')   = " ab"
+     * StringUtils.center("abcd", 2, ' ') = "abcd"
+     * StringUtils.center("a", 4, ' ')    = " a  "
+     * StringUtils.center("a", 4, 'y')    = "yayy"
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padChar the character to pad the new String with + * @return centered String, {@code null} if null String input + * @since 2.0 + */ + public static String center(String str, 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; + } + + /** + *

Centers a String in a larger String of size {@code size}. + * Uses a supplied String as the value to pad the String with.

+ * + *

If the size is less than the String length, the String is returned. + * A {@code null} String returns {@code null}. + * A negative size is treated as zero.

+ * + *
+     * StringUtils.center(null, *, *)     = null
+     * StringUtils.center("", 4, " ")     = "    "
+     * StringUtils.center("ab", -1, " ")  = "ab"
+     * StringUtils.center("ab", 4, " ")   = " ab"
+     * StringUtils.center("abcd", 2, " ") = "abcd"
+     * StringUtils.center("a", 4, " ")    = " a  "
+     * StringUtils.center("a", 4, "yz")   = "yayz"
+     * StringUtils.center("abc", 7, null) = "  abc  "
+     * StringUtils.center("abc", 7, "")   = "  abc  "
+     * 
+ * + * @param str the String to center, may be null + * @param size the int size of new String, negative treated as zero + * @param padStr the String to pad the new String with, must not be null or empty + * @return centered String, {@code null} if null String input + * @throws IllegalArgumentException if padStr is {@code null} or empty + */ + public static String center(String str, 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 + //----------------------------------------------------------------------- + /** + *

Converts a String to upper case as per {@link String#toUpperCase()}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.upperCase(null)  = null
+     * StringUtils.upperCase("")    = ""
+     * StringUtils.upperCase("aBc") = "ABC"
+     * 
+ * + *

Note: As described in the documentation for {@link String#toUpperCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

+ * + * @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(); + } + + /** + *

Converts a String to upper case as per {@link String#toUpperCase(Locale)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.upperCase(null, Locale.ENGLISH)  = null
+     * StringUtils.upperCase("", Locale.ENGLISH)    = ""
+     * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+     * 
+ * + * @param str the String to upper case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the upper cased String, {@code null} if null String input + * @since 2.5 + */ + public static String upperCase(String str, Locale locale) { + if (str == null) { + return null; + } + return str.toUpperCase(locale); + } + + /** + *

Converts a String to lower case as per {@link String#toLowerCase()}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.lowerCase(null)  = null
+     * StringUtils.lowerCase("")    = ""
+     * StringUtils.lowerCase("aBc") = "abc"
+     * 
+ * + *

Note: As described in the documentation for {@link String#toLowerCase()}, + * the result of this method is affected by the current locale. + * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)} + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

+ * + * @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(); + } + + /** + *

Converts a String to lower case as per {@link String#toLowerCase(Locale)}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.lowerCase(null, Locale.ENGLISH)  = null
+     * StringUtils.lowerCase("", Locale.ENGLISH)    = ""
+     * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+     * 
+ * + * @param str the String to lower case, may be null + * @param locale the locale that defines the case transformation rules, must not be null + * @return the lower cased String, {@code null} if null String input + * @since 2.5 + */ + public static String lowerCase(String str, Locale locale) { + if (str == null) { + return null; + } + return str.toLowerCase(locale); + } + + /** + *

Capitalizes a String changing the first letter to title case as + * per {@link Character#toTitleCase(char)}. No other letters are changed.

+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#capitalize(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.capitalize(null)  = null
+     * StringUtils.capitalize("")    = ""
+     * StringUtils.capitalize("cat") = "Cat"
+     * StringUtils.capitalize("cAt") = "CAt"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return the capitalized String, {@code null} if null String input + * @see 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(); + } + + /** + *

Uncapitalizes a String changing the first letter to title case as + * per {@link Character#toLowerCase(char)}. No other letters are changed.

+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#uncapitalize(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.uncapitalize(null)  = null
+     * StringUtils.uncapitalize("")    = ""
+     * StringUtils.uncapitalize("Cat") = "cat"
+     * StringUtils.uncapitalize("CAT") = "cAT"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @return the uncapitalized String, {@code null} if null String input + * @see 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(); + } + + /** + *

Swaps the case of a String changing upper and title case to + * lower case, and lower case to upper case.

+ * + *
    + *
  • Upper case character converts to Lower case
  • + *
  • Title case character converts to Lower case
  • + *
  • Lower case character converts to Upper case
  • + *
+ * + *

For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#swapCase(String)}. + * A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.swapCase(null)                 = null
+     * StringUtils.swapCase("")                   = ""
+     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+     * 
+ * + *

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.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Counts how many times the substring appears in the larger string.

+ * + *

A {@code null} or empty ("") String input returns {@code 0}.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Checks if the CharSequence contains only Unicode letters.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAlpha(null)   = false
+     * StringUtils.isAlpha("")     = false
+     * StringUtils.isAlpha("  ")   = false
+     * StringUtils.isAlpha("abc")  = true
+     * StringUtils.isAlpha("ab2c") = false
+     * StringUtils.isAlpha("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, and is non-null + * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlpha(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; + } + + /** + *

Checks if the CharSequence contains only Unicode letters and + * space (' ').

+ * + *

{@code null} will return {@code false} + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isAlphaSpace(null)   = false
+     * StringUtils.isAlphaSpace("")     = true
+     * StringUtils.isAlphaSpace("  ")   = true
+     * StringUtils.isAlphaSpace("abc")  = true
+     * StringUtils.isAlphaSpace("ab c") = true
+     * StringUtils.isAlphaSpace("ab2c") = false
+     * StringUtils.isAlphaSpace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters and space, + * and is non-null + * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence) + */ + public static boolean isAlphaSpace(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; + } + + /** + *

Checks if the CharSequence contains only Unicode letters or digits.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAlphanumeric(null)   = false
+     * StringUtils.isAlphanumeric("")     = false
+     * StringUtils.isAlphanumeric("  ")   = false
+     * StringUtils.isAlphanumeric("abc")  = true
+     * StringUtils.isAlphanumeric("ab c") = false
+     * StringUtils.isAlphanumeric("ab2c") = true
+     * StringUtils.isAlphanumeric("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters or digits, + * and is non-null + * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isAlphanumeric(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; + } + + /** + *

Checks if the CharSequence contains only Unicode letters, digits + * or space ({@code ' '}).

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isAlphanumericSpace(null)   = false
+     * StringUtils.isAlphanumericSpace("")     = true
+     * StringUtils.isAlphanumericSpace("  ")   = true
+     * StringUtils.isAlphanumericSpace("abc")  = true
+     * StringUtils.isAlphanumericSpace("ab c") = true
+     * StringUtils.isAlphanumericSpace("ab2c") = true
+     * StringUtils.isAlphanumericSpace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains letters, digits or space, + * and is non-null + * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence) + */ + public static boolean isAlphanumericSpace(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; + } + + /** + *

Checks if the CharSequence contains only ASCII printable characters.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Checks if the CharSequence contains only Unicode digits. + * A decimal point is not a Unicode digit and returns false.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Checks if the CharSequence contains only Unicode digits or space + * ({@code ' '}). + * A decimal point is not a Unicode digit and returns false.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * 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
+     * 
+ * + * @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; + } + + /** + *

Checks if the CharSequence contains only whitespace.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code true}.

+ * + *
+     * StringUtils.isWhitespace(null)   = false
+     * StringUtils.isWhitespace("")     = true
+     * StringUtils.isWhitespace("  ")   = true
+     * StringUtils.isWhitespace("abc")  = false
+     * StringUtils.isWhitespace("ab2c") = false
+     * StringUtils.isWhitespace("ab-c") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains whitespace, and is non-null + * @since 2.0 + * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence) + */ + public static boolean isWhitespace(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; + } + + /** + *

Checks if the CharSequence contains only lowercase characters.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAllLowerCase(null)   = false
+     * StringUtils.isAllLowerCase("")     = false
+     * StringUtils.isAllLowerCase("  ")   = false
+     * StringUtils.isAllLowerCase("abc")  = true
+     * StringUtils.isAllLowerCase("abC") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains lowercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence) + */ + public static boolean isAllLowerCase(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; + } + + /** + *

Checks if the CharSequence contains only uppercase characters.

+ * + *

{@code null} will return {@code false}. + * An empty String (length()=0) will return {@code false}.

+ * + *
+     * StringUtils.isAllUpperCase(null)   = false
+     * StringUtils.isAllUpperCase("")     = false
+     * StringUtils.isAllUpperCase("  ")   = false
+     * StringUtils.isAllUpperCase("ABC")  = true
+     * StringUtils.isAllUpperCase("aBC") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains uppercase characters, and is non-null + * @since 2.5 + * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence) + */ + public static boolean isAllUpperCase(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 + //----------------------------------------------------------------------- + /** + *

Returns either the passed in String, + * or if the String is {@code null}, an empty String ("").

+ * + *
+     * StringUtils.defaultString(null)  = ""
+     * StringUtils.defaultString("")    = ""
+     * StringUtils.defaultString("bat") = "bat"
+     * 
+ * + * @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; + } + + /** + *

Returns either the passed in String, or if the String is + * {@code null}, the value of {@code defaultStr}.

+ * + *
+     * StringUtils.defaultString(null, "NULL")  = "NULL"
+     * StringUtils.defaultString("", "NULL")    = ""
+     * StringUtils.defaultString("bat", "NULL") = "bat"
+     * 
+ * + * @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; + } + + /** + *

Returns either the passed in CharSequence, or if the CharSequence is + * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.

+ * + *
+     * StringUtils.defaultIfBlank(null, "NULL")  = "NULL"
+     * StringUtils.defaultIfBlank("", "NULL")    = "NULL"
+     * StringUtils.defaultIfBlank(" ", "NULL")   = "NULL"
+     * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+     * StringUtils.defaultIfBlank("", null)      = null
+     * 
+ * @param 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 defaultIfBlank(T str, T defaultStr) { + return StringUtils.isBlank(str) ? defaultStr : str; + } + + /** + *

Returns either the passed in CharSequence, or if the CharSequence is + * empty or {@code null}, the value of {@code defaultStr}.

+ * + *
+     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
+     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
+     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+     * StringUtils.defaultIfEmpty("", null)      = null
+     * 
+ * @param 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 defaultIfEmpty(T str, T defaultStr) { + return StringUtils.isEmpty(str) ? defaultStr : str; + } + + // Reversing + //----------------------------------------------------------------------- + /** + *

Reverses a String as per {@link StringBuilder#reverse()}.

+ * + *

A {@code null} String returns {@code null}.

+ * + *
+     * StringUtils.reverse(null)  = null
+     * StringUtils.reverse("")    = ""
+     * StringUtils.reverse("bat") = "tab"
+     * 
+ * + * @param str the String to reverse, may be null + * @return the reversed String, {@code null} if null String input + */ + public static String reverse(String str) { + if (str == null) { + return null; + } + return new StringBuilder(str).reverse().toString(); + } + + /** + *

Reverses a String that is delimited by a specific character.

+ * + *

The Strings between the delimiters are not reversed. + * Thus java.lang.String becomes String.lang.java (if the delimiter + * is {@code '.'}).

+ * + *
+     * StringUtils.reverseDelimited(null, *)      = null
+     * StringUtils.reverseDelimited("", *)        = ""
+     * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
+     * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Abbreviates a String using ellipses. This will turn + * "Now is the time for all good men" into "Now is the time for..."

+ * + *

Specifically: + *

    + *
  • If {@code str} is less than {@code maxWidth} characters + * long, return it.
  • + *
  • Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.
  • + *
  • If {@code maxWidth} is less than {@code 4}, throw an + * {@code IllegalArgumentException}.
  • + *
  • In no case will it return a String of length greater than + * {@code maxWidth}.
  • + *
+ *

+ * + *
+     * StringUtils.abbreviate(null, *)      = null
+     * StringUtils.abbreviate("", 4)        = ""
+     * StringUtils.abbreviate("abcdefg", 6) = "abc..."
+     * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
+     * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
+     * StringUtils.abbreviate("abcdefg", 4) = "a..."
+     * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
+     * 
+ * + * @param str the String to check, may be null + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(String str, int maxWidth) { + return abbreviate(str, 0, maxWidth); + } + + /** + *

Abbreviates a String using ellipses. This will turn + * "Now is the time for all good men" into "...is the time for..."

+ * + *

Works like {@code abbreviate(String, int)}, but allows you to specify + * a "left edge" offset. Note that this left edge is not necessarily going to + * be the leftmost character in the result, or the first character following the + * ellipses, but it will appear somewhere in the result. + * + *

In no case will it return a String of length greater than + * {@code maxWidth}.

+ * + *
+     * StringUtils.abbreviate(null, *, *)                = null
+     * StringUtils.abbreviate("", 0, 4)                  = ""
+     * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
+     * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
+     * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
+     * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
+     * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
+     * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
+     * 
+ * + * @param str the String to check, may be null + * @param offset left edge of source String + * @param maxWidth maximum length of result String, must be at least 4 + * @return abbreviated String, {@code null} if null String input + * @throws IllegalArgumentException if the width is too small + * @since 2.0 + */ + public static String abbreviate(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)); + } + + /** + *

Abbreviates a String to the length passed, replacing the middle characters with the supplied + * replacement String.

+ * + *

This abbreviation only occurs if the following criteria is met: + *

    + *
  • Neither the String for abbreviation nor the replacement String are null or empty
  • + *
  • The length to truncate to is less than the length of the supplied String
  • + *
  • The length to truncate to is greater than 0
  • + *
  • 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
  • + *
+ * Otherwise, the returned String will be the same as the supplied String for abbreviation. + *

+ * + *
+     * StringUtils.abbreviateMiddle(null, null, 0)      = null
+     * StringUtils.abbreviateMiddle("abc", null, 0)      = "abc"
+     * StringUtils.abbreviateMiddle("abc", ".", 0)      = "abc"
+     * StringUtils.abbreviateMiddle("abc", ".", 3)      = "abc"
+     * StringUtils.abbreviateMiddle("abcdef", ".", 4)     = "ab.f"
+     * 
+ * + * @param str the String to abbreviate, may be null + * @param middle the String to replace the middle characters with, may be null + * @param length the length to abbreviate {@code str} to. + * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation. + * @since 2.5 + */ + public static String abbreviateMiddle(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 + //----------------------------------------------------------------------- + /** + *

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.)

+ * + *

For example, + * {@code difference("i am a machine", "i am a robot") -> "robot"}.

+ * + *
+     * 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"
+     * 
+ * + * @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); + } + + /** + *

Compares two CharSequences, and returns the index at which the + * CharSequences begin to differ.

+ * + *

For example, + * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}

+ * + *
+     * StringUtils.indexOfDifference(null, null) = -1
+     * StringUtils.indexOfDifference("", "") = -1
+     * StringUtils.indexOfDifference("", "abc") = 0
+     * StringUtils.indexOfDifference("abc", "") = 0
+     * StringUtils.indexOfDifference("abc", "abc") = -1
+     * StringUtils.indexOfDifference("ab", "abxyz") = 2
+     * StringUtils.indexOfDifference("abcde", "abxyz") = 2
+     * StringUtils.indexOfDifference("abcde", "xyz") = 0
+     * 
+ * + * @param cs1 the first CharSequence, may be null + * @param cs2 the second CharSequence, may be null + * @return the index where cs1 and cs2 begin to differ; -1 if they are equal + * @since 2.0 + * @since 3.0 Changed signature from indexOfDifference(String, String) to + * indexOfDifference(CharSequence, CharSequence) + */ + public static int indexOfDifference(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; + } + + /** + *

Compares all CharSequences in an array and returns the index at which the + * CharSequences begin to differ.

+ * + *

For example, + * indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7

+ * + *
+     * StringUtils.indexOfDifference(null) = -1
+     * StringUtils.indexOfDifference(new String[] {}) = -1
+     * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
+     * StringUtils.indexOfDifference(new String[] {null, null}) = -1
+     * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
+     * StringUtils.indexOfDifference(new String[] {"", null}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
+     * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
+     * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
+     * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
+     * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
+     * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
+     * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
+     * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
+     * 
+ * + * @param css array of CharSequences, entries may be null + * @return the index where the strings begin to differ; -1 if they are all equal + * @since 2.4 + * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...) + */ + public static int indexOfDifference(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; + } + + /** + *

Compares all Strings in an array and returns the initial sequence of + * characters that is common to all of them.

+ * + *

For example, + * getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -> "i am a "

+ * + *
+     * StringUtils.getCommonPrefix(null) = ""
+     * StringUtils.getCommonPrefix(new String[] {}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
+     * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
+     * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
+     * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
+     * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
+     * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
+     * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
+     * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
+     * 
+ * + * @param strs array of String objects, entries may be null + * @return the initial sequence of characters that are common to all Strings + * in the array; empty String if the array is null, the elements are all null + * or if there is no common prefix. + * @since 2.4 + */ + public static String getCommonPrefix(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 + //----------------------------------------------------------------------- + /** + *

Find the Levenshtein distance between two Strings.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

The previous implementation of the Levenshtein distance algorithm + * was from http://www.merriampark.com/ld.htm

+ * + *

Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError + * which can occur when my Java implementation is used with very large strings.
+ * This implementation of the Levenshtein distance algorithm + * is from http://www.merriampark.com/ldjava.htm

+ * + *
+     * StringUtils.getLevenshteinDistance(null, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, null)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance("","")               = 0
+     * StringUtils.getLevenshteinDistance("","a")              = 1
+     * StringUtils.getLevenshteinDistance("aaapppp", "")       = 7
+     * StringUtils.getLevenshteinDistance("frog", "fog")       = 1
+     * StringUtils.getLevenshteinDistance("fly", "ant")        = 3
+     * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+     * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+     * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+     * StringUtils.getLevenshteinDistance("hello", "hallo")    = 1
+     * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input {@code null} + * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to + * getLevenshteinDistance(CharSequence, CharSequence) + */ + public static int getLevenshteinDistance(CharSequence s, CharSequence t) { + if (s == null || t == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + + /* + The difference between this impl. and the previous is that, rather + than creating and retaining a matrix of size s.length() + 1 by t.length() + 1, + we maintain two single-dimensional arrays of length s.length() + 1. The first, d, + is the 'current working' distance array that maintains the newest distance cost + counts as we iterate through the characters of String s. Each time we increment + the index of String t we are comparing, d is copied to p, the second int[]. Doing so + allows us to retain the previous cost counts as required by the algorithm (taking + the minimum of the cost count to the left, up one, and diagonally up and to the left + of the current cost count being calculated). (Note that the arrays aren't really + copied anymore, just switched...this is clearly much better than cloning an array + or doing a System.arraycopy() each time through the outer loop.) + + Effectively, the difference between the two implementations is this one does not + cause an out of memory condition when calculating the LD over two very large strings. + */ + + int n = s.length(); // length of s + int m = t.length(); // length of t + + if (n == 0) { + return m; + } else if (m == 0) { + return n; + } + + if (n > m) { + // swap the input strings to consume less memory + 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]; + } + + /** + *

Find the Levenshtein distance between two Strings if it's less than or equal to a given + * threshold.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield + * and Chas Emerick's implementation of the Levenshtein distance algorithm from + * http://www.merriampark.com/ld.htm

+ * + *
+     * StringUtils.getLevenshteinDistance(null, *, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, null, *)             = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance(*, *, -1)               = IllegalArgumentException
+     * StringUtils.getLevenshteinDistance("","", 0)               = 0
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 8)       = 7
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 7)       = 7
+     * StringUtils.getLevenshteinDistance("aaapppp", "", 6))      = -1
+     * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
+     * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
+     * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
+     * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
+     * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @param threshold the target threshold, must not be negative + * @return result distance, or {@code -1} if the distance would be greater than the threshold + * @throws IllegalArgumentException if either String input {@code null} or negative threshold + */ + public static int getLevenshteinDistance(CharSequence s, CharSequence t, 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 + //----------------------------------------------------------------------- + + /** + *

Check if a CharSequence starts with a specified prefix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Case insensitive check if a CharSequence starts with a specified prefix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Check if a CharSequence starts with a specified prefix (optionally case insensitive).

+ * + * @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()); + } + + /** + *

Check if a CharSequence starts with any of an array of specified strings.

+ * + *
+     * 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
+     * 
+ * + * @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 + //----------------------------------------------------------------------- + + /** + *

Check if a CharSequence ends with a specified suffix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Case insensitive check if a CharSequence ends with a specified suffix.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case insensitive.

+ * + *
+     * 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
+     * 
+ * + * @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); + } + + /** + *

Check if a CharSequence ends with a specified suffix (optionally case insensitive).

+ * + * @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()); + } + + /** + *

+ * Similar to http://www.w3.org/TR/xpath/#function-normalize + * -space + *

+ *

+ * The function returns the argument string with whitespace normalized by using + * {@link #trim(String)} to remove leading and trailing whitespace + * and then replacing sequences of whitespace characters by a single space. + *

+ * In XML Whitespace characters are the same as those allowed by the S production, which is S ::= (#x20 | #x9 | #xD | #xA)+ + *

+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r] + *

+ * For reference: + *

    + *
  • \x0B = vertical tab
  • + *
  • \f = #xC = form feed
  • + *
  • #x20 = space
  • + *
  • #x9 = \t
  • + *
  • #xA = \n
  • + *
  • #xD = \r
  • + *
+ *

+ *

+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also + * normalize. Additionally {@link #trim(String)} removes control characters (char <= 32) from both + * ends of this String. + *

+ * + * @see Pattern + * @see #trim(String) + * @see http://www.w3.org/TR/xpath/#function-normalize-space + * @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(" "); + } + + /** + *

Check if a CharSequence ends with any of an array of specified strings.

+ * + *
+     * 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
+     * 
+ * + * @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; + +/** + *

+ * Helpers for {@code java.lang.System}. + *

+ *

+ * 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}. + *

+ *

+ * #ThreadSafe# + *

+ * + * @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"; + + /** + *

+ * The {@code awt.toolkit} System Property. + *

+ *

+ * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}. + *

+ *

+ * On platforms without a GUI, this value is {@code null}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.1 + */ + public static final String AWT_TOOLKIT = getSystemProperty("awt.toolkit"); + + /** + *

+ * The {@code file.encoding} System Property. + *

+ *

+ * File encoding, such as {@code Cp1252}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String FILE_ENCODING = getSystemProperty("file.encoding"); + + /** + *

+ * The {@code file.separator} System Property. File separator ("/" on UNIX). + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String FILE_SEPARATOR = getSystemProperty("file.separator"); + + /** + *

+ * The {@code java.awt.fonts} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_FONTS = getSystemProperty("java.awt.fonts"); + + /** + *

+ * The {@code java.awt.graphicsenv} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_GRAPHICSENV = getSystemProperty("java.awt.graphicsenv"); + + /** + *

+ * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or + * {@code "false"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @see #isJavaAwtHeadless() + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_AWT_HEADLESS = getSystemProperty("java.awt.headless"); + + /** + *

+ * The {@code java.awt.printerjob} System Property. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.1 + */ + public static final String JAVA_AWT_PRINTERJOB = getSystemProperty("java.awt.printerjob"); + + /** + *

+ * The {@code java.class.path} System Property. Java class path. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_PATH = getSystemProperty("java.class.path"); + + /** + *

+ * The {@code java.class.version} System Property. Java class format version number. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_CLASS_VERSION = getSystemProperty("java.class.version"); + + /** + *

+ * 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. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2. Not used in Sun versions after 1.2. + */ + public static final String JAVA_COMPILER = getSystemProperty("java.compiler"); + + /** + *

+ * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.4 + */ + public static final String JAVA_ENDORSED_DIRS = getSystemProperty("java.endorsed.dirs"); + + /** + *

+ * The {@code java.ext.dirs} System Property. Path of extension directory or directories. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.3 + */ + public static final String JAVA_EXT_DIRS = getSystemProperty("java.ext.dirs"); + + /** + *

+ * The {@code java.home} System Property. Java installation directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_HOME = getSystemProperty(JAVA_HOME_KEY); + + /** + *

+ * The {@code java.io.tmpdir} System Property. Default temp file path. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_IO_TMPDIR = getSystemProperty(JAVA_IO_TMPDIR_KEY); + + /** + *

+ * The {@code java.library.path} System Property. List of paths to search when loading libraries. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_LIBRARY_PATH = getSystemProperty("java.library.path"); + + /** + *

+ * The {@code java.runtime.name} System Property. Java Runtime Environment name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_NAME = getSystemProperty("java.runtime.name"); + + /** + *

+ * The {@code java.runtime.version} System Property. Java Runtime Environment version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.3 + */ + public static final String JAVA_RUNTIME_VERSION = getSystemProperty("java.runtime.version"); + + /** + *

+ * The {@code java.specification.name} System Property. Java Runtime Environment specification name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_NAME = getSystemProperty("java.specification.name"); + + /** + *

+ * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_SPECIFICATION_VENDOR = getSystemProperty("java.specification.vendor"); + + /** + *

+ * The {@code java.specification.version} System Property. Java Runtime Environment specification version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @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); + + /** + *

+ * The {@code java.util.prefs.PreferencesFactory} System Property. A class name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.1 + * @since Java 1.4 + */ + public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY = + getSystemProperty("java.util.prefs.PreferencesFactory"); + + /** + *

+ * The {@code java.vendor} System Property. Java vendor-specific string. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR = getSystemProperty("java.vendor"); + + /** + *

+ * The {@code java.vendor.url} System Property. Java vendor URL. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VENDOR_URL = getSystemProperty("java.vendor.url"); + + /** + *

+ * The {@code java.version} System Property. Java version number. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String JAVA_VERSION = getSystemProperty("java.version"); + + /** + *

+ * The {@code java.vm.info} System Property. Java Virtual Machine implementation info. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String JAVA_VM_INFO = getSystemProperty("java.vm.info"); + + /** + *

+ * The {@code java.vm.name} System Property. Java Virtual Machine implementation name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_NAME = getSystemProperty("java.vm.name"); + + /** + *

+ * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_NAME = getSystemProperty("java.vm.specification.name"); + + /** + *

+ * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VENDOR = getSystemProperty("java.vm.specification.vendor"); + + /** + *

+ * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_SPECIFICATION_VERSION = getSystemProperty("java.vm.specification.version"); + + /** + *

+ * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_VENDOR = getSystemProperty("java.vm.vendor"); + + /** + *

+ * The {@code java.vm.version} System Property. Java Virtual Machine implementation version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.2 + */ + public static final String JAVA_VM_VERSION = getSystemProperty("java.vm.version"); + + /** + *

+ * The {@code line.separator} System Property. Line separator ("\n" on UNIX). + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String LINE_SEPARATOR = getSystemProperty("line.separator"); + + /** + *

+ * The {@code os.arch} System Property. Operating system architecture. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String OS_ARCH = getSystemProperty("os.arch"); + + /** + *

+ * The {@code os.name} System Property. Operating system name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String OS_NAME = getSystemProperty("os.name"); + + /** + *

+ * The {@code os.version} System Property. Operating system version. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String OS_VERSION = getSystemProperty("os.version"); + + /** + *

+ * The {@code path.separator} System Property. Path separator (":" on UNIX). + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String PATH_SEPARATOR = getSystemProperty("path.separator"); + + /** + *

+ * 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 + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_COUNTRY = getSystemProperty("user.country") == null ? + getSystemProperty("user.region") : getSystemProperty("user.country"); + + /** + *

+ * The {@code user.dir} System Property. User's current working directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String USER_DIR = getSystemProperty(USER_DIR_KEY); + + /** + *

+ * The {@code user.home} System Property. User's home directory. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String USER_HOME = getSystemProperty(USER_HOME_KEY); + + /** + *

+ * The {@code user.language} System Property. User's language code, such as {@code "en"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since 2.0 + * @since Java 1.2 + */ + public static final String USER_LANGUAGE = getSystemProperty("user.language"); + + /** + *

+ * The {@code user.name} System Property. User's account name. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @since Java 1.1 + */ + public static final String USER_NAME = getSystemProperty("user.name"); + + /** + *

+ * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}. + *

+ *

+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does + * not exist. + *

+ *

+ * 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. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is Java version 1.1 (also 1.1.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1"); + + /** + *

+ * Is {@code true} if this is Java version 1.2 (also 1.2.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2"); + + /** + *

+ * Is {@code true} if this is Java version 1.3 (also 1.3.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3"); + + /** + *

+ * Is {@code true} if this is Java version 1.4 (also 1.4.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4"); + + /** + *

+ * Is {@code true} if this is Java version 1.5 (also 1.5.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5"); + + /** + *

+ * Is {@code true} if this is Java version 1.6 (also 1.6.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ */ + public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6"); + + /** + *

+ * Is {@code true} if this is Java version 1.7 (also 1.7.x versions). + *

+ *

+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is AIX. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_AIX = getOSMatchesName("AIX"); + + /** + *

+ * Is {@code true} if this is HP-UX. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_HP_UX = getOSMatchesName("HP-UX"); + + /** + *

+ * Is {@code true} if this is Irix. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_IRIX = getOSMatchesName("Irix"); + + /** + *

+ * Is {@code true} if this is Linux. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_LINUX = getOSMatchesName("Linux") || getOSMatchesName("LINUX"); + + /** + *

+ * Is {@code true} if this is Mac. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_MAC = getOSMatchesName("Mac"); + + /** + *

+ * Is {@code true} if this is Mac. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_MAC_OSX = getOSMatchesName("Mac OS X"); + + /** + *

+ * Is {@code true} if this is FreeBSD. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.0.2 + */ + public static final boolean IS_OS_FREE_BSD = getOSMatchesName("FreeBSD"); + + /** + *

+ * Is {@code true} if this is OpenBSD. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.0.2 + */ + public static final boolean IS_OS_OPEN_BSD = getOSMatchesName("OpenBSD"); + + /** + *

+ * Is {@code true} if this is NetBSD. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.0.2 + */ + public static final boolean IS_OS_NET_BSD = getOSMatchesName("NetBSD"); + + /** + *

+ * Is {@code true} if this is OS/2. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_OS2 = getOSMatchesName("OS/2"); + + /** + *

+ * Is {@code true} if this is Solaris. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_SOLARIS = getOSMatchesName("Solaris"); + + /** + *

+ * Is {@code true} if this is SunOS. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_SUN_OS = getOSMatchesName("SunOS"); + + /** + *

+ * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @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; + + /** + *

+ * Is {@code true} if this is Windows. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS = getOSMatchesName(OS_NAME_WINDOWS_PREFIX); + + /** + *

+ * Is {@code true} if this is Windows 2000. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_2000 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.0"); + + /** + *

+ * Is {@code true} if this is Windows 95. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is Windows 98. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is Windows ME. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is Windows NT. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @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 + + /** + *

+ * Is {@code true} if this is Windows XP. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.0 + */ + public static final boolean IS_OS_WINDOWS_XP = getOSMatches(OS_NAME_WINDOWS_PREFIX, "5.1"); + + // ----------------------------------------------------------------------- + /** + *

+ * Is {@code true} if this is Windows Vista. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 2.4 + */ + public static final boolean IS_OS_WINDOWS_VISTA = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.0"); + + /** + *

+ * Is {@code true} if this is Windows 7. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ * + * @since 3.0 + */ + public static final boolean IS_OS_WINDOWS_7 = getOSMatches(OS_NAME_WINDOWS_PREFIX, "6.1"); + + /** + *

+ * Gets the Java home directory as a {@code File}. + *

+ * + * @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)); + } + + /** + *

+ * Gets the Java IO temporary directory as a {@code File}. + *

+ * + * @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)); + } + + /** + *

+ * Decides if the Java version matches. + *

+ * + * @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); + } + + // ----------------------------------------------------------------------- + /** + *

+ * Gets a System property, defaulting to {@code null} if the property cannot be read. + *

+ *

+ * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to + * {@code System.err}. + *

+ * + * @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; + } + } + + /** + *

+ * Gets the user directory as a {@code File}. + *

+ * + * @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)); + } + + /** + *

+ * Gets the user home directory as a {@code File}. + *

+ * + * @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; + } + + /** + *

+ * Is the Java version at least the requested version. + *

+ *

+ * Example input: + *

+ *
    + *
  • {@code 1.2f} to test for Java 1.2
  • + *
  • {@code 1.31f} to test for Java 1.3.1
  • + *
+ * + * @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); + } + + /** + *

+ * Decides if the Java version matches. + *

+ *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @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. + *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @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. + *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @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); + } + + // ----------------------------------------------------------------------- + /** + *

+ * SystemUtils instances should NOT be constructed in standard programming. Instead, the class should be used as + * {@code SystemUtils.FILE_SEPARATOR}. + *

+ *

+ * This constructor is public to permit tools that require a JavaBean instance to operate. + *

+ */ + 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; + +/** + *

This class assists in validating arguments. The validation methods are + * based along the following principles: + *

    + *
  • An invalid {@code null} argument causes a {@link NullPointerException}.
  • + *
  • A non-{@code null} argument causes an {@link IllegalArgumentException}.
  • + *
  • An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.
  • + *
+ * + *

All exceptions messages are + * format strings + * as defined by the Java platform. For example:

+ * + *
+ * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
+ * Validate.notNull(surname, "The surname must not be %s", null);
+ * 
+ * + *

#ThreadSafe#

+ * @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 + //--------------------------------------------------------------------------------- + + /** + *

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.

+ * + *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ * + *

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.

+ * + * @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))); + } + } + + /** + *

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.

+ * + *
Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
+ * + *

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.

+ * + * @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))); + } + } + + /** + *

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.

+ * + *
+     * 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");
+ * + * @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)); + } + } + + /** + *

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.

+ * + *
+     * Validate.isTrue(i > 0);
+     * Validate.isTrue(myObject.isOk());
+ * + *

The message of the exception is "The validated expression is + * false".

+ * + * @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 + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + *

The message of the exception is "The validated object is + * null".

+ * + * @param 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 notNull(T object) { + return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + * @param 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 notNull(T object, String message, Object... values) { + if (object == null) { + throw new NullPointerException(String.format(message, values)); + } + return object; + } + + // notEmpty array + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.notEmpty(myArray, "The array must not be empty");
+ * + * @param 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[] 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; + } + + /** + *

Validate that the specified argument array is neither {@code null} + * nor a length of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myArray);
+ * + *

The message in the exception is "The validated array is + * empty". + * + * @param 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[] notEmpty(T[] array) { + return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + + // notEmpty collection + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.notEmpty(myCollection, "The collection must not be empty");
+ * + * @param 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 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; + } + + /** + *

Validate that the specified argument collection is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myCollection);
+ * + *

The message in the exception is "The validated collection is + * empty".

+ * + * @param 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 notEmpty(T collection) { + return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + + // notEmpty map + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.notEmpty(myMap, "The map must not be empty");
+ * + * @param 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 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; + } + + /** + *

Validate that the specified argument map is neither {@code null} + * nor a size of zero (no elements); otherwise throwing an exception. + * + *

Validate.notEmpty(myMap);
+ * + *

The message in the exception is "The validated map is + * empty".

+ * + * @param 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 notEmpty(T map) { + return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + + // notEmpty string + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.notEmpty(myString, "The string must not be empty");
+ * + * @param 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 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; + } + + /** + *

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. + * + *

Validate.notEmpty(myString);
+ * + *

The message in the exception is "The validated + * character sequence is empty".

+ * + * @param 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 notEmpty(T chars) { + return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + + // notBlank string + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.notBlank(myString, "The string must not be blank");
+ * + * @param 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 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; + } + + /** + *

Validate that the specified argument character sequence is + * neither {@code null}, a length of zero (no characters), empty + * nor whitespace; otherwise throwing an exception. + * + *

Validate.notBlank(myString);
+ * + *

The message in the exception is "The validated character + * sequence is blank".

+ * + * @param 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 notBlank(T chars) { + return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE); + } + + // noNullElements array + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.noNullElements(myArray, "The array contain null at position %d");
+ * + *

If the array is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

+ * + * @param 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[] 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; + } + + /** + *

Validate that the specified argument array is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception. + * + *

Validate.noNullElements(myArray);
+ * + *

If the array is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the message in the + * exception is "The validated array contains null element at index: + * " followed by the index.

+ * + * @param 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[] noNullElements(T[] array) { + return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); + } + + // noNullElements iterable + //--------------------------------------------------------------------------------- + + /** + *

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. + * + *

Validate.noNullElements(myCollection, "The collection contains null at position %d");
+ * + *

If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the iterable has a {@code null} element, then the iteration + * index of the invalid element is appended to the {@code values} + * argument.

+ * + * @param 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 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; + } + + /** + *

Validate that the specified argument iterable is neither + * {@code null} nor contains any elements that are {@code null}; + * otherwise throwing an exception. + * + *

Validate.noNullElements(myCollection);
+ * + *

If the iterable is {@code null}, then the message in the exception + * is "The validated object is null".

+ * + *

If the array has a {@code null} element, then the message in the + * exception is "The validated iterable contains null element at index: + * " followed by the index.

+ * + * @param 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 noNullElements(T iterable) { + return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE); + } + + // validIndex array + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception with the specified message.

+ * + *
Validate.validIndex(myArray, 2, "The array index is invalid: ");
+ * + *

If the array is {@code null}, then the message of the exception + * is "The validated object is null".

+ * + * @param 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[] 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; + } + + /** + *

Validates that the index is within the bounds of the argument + * array; otherwise throwing an exception.

+ * + *
Validate.validIndex(myArray, 2);
+ * + *

If the array is {@code null}, then the message of the exception + * is "The validated object is null".

+ * + *

If the index is invalid, then the message of the exception is + * "The validated array index is invalid: " followed by the + * index.

+ * + * @param 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[] validIndex(T[] array, int index) { + return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex collection + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception with the specified message.

+ * + *
Validate.validIndex(myCollection, 2, "The collection index is invalid: ");
+ * + *

If the collection is {@code null}, then the message of the + * exception is "The validated object is null".

+ * + * @param 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 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; + } + + /** + *

Validates that the index is within the bounds of the argument + * collection; otherwise throwing an exception.

+ * + *
Validate.validIndex(myCollection, 2);
+ * + *

If the index is invalid, then the message of the exception + * is "The validated collection index is invalid: " + * followed by the index.

+ * + * @param 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 validIndex(T collection, int index) { + return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); + } + + // validIndex string + //--------------------------------------------------------------------------------- + + /** + *

Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception with the + * specified message.

+ * + *
Validate.validIndex(myStr, 2, "The string index is invalid: ");
+ * + *

If the character sequence is {@code null}, then the message + * of the exception is "The validated object is null".

+ * + * @param 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 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; + } + + /** + *

Validates that the index is within the bounds of the argument + * character sequence; otherwise throwing an exception.

+ * + *
Validate.validIndex(myStr, 2);
+ * + *

If the character sequence is {@code null}, then the message + * of the exception is "The validated object is + * null".

+ * + *

If the index is invalid, then the message of the exception + * is "The validated character sequence index is invalid: " + * followed by the index.

+ * + * @param 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 validIndex(T chars, int index) { + return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); + } + + // validState + //--------------------------------------------------------------------------------- + + /** + *

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.

+ * + *
+     * Validate.validState(field > 0);
+     * Validate.validState(this.isOk());
+ * + *

The message of the exception is "The validated state is + * false".

+ * + * @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); + } + } + + /** + *

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.

+ * + *
Validate.validState(this.isOk(), "The state is not OK: %s", myObject);
+ * + * @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 + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception.

+ * + *
Validate.matchesPattern("hi", "[a-z]*");
+ * + *

The syntax of the pattern is the one used in the {@link Pattern} class.

+ * + * @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)); + } + } + + /** + *

Validate that the specified argument character sequence matches the specified regular + * expression pattern; otherwise throwing an exception with the specified message.

+ * + *
Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");
+ * + *

The syntax of the pattern is the one used in the {@link Pattern} class.

+ * + * @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 + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception.

+ * + *
Validate.inclusiveBetween(0, 2, 1);
+ * + * @param 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 void inclusiveBetween(T start, T end, Comparable value) { + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message.

+ * + *
Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
+ * + * @param 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 void inclusiveBetween(T start, T end, Comparable value, String message, Object... values) { + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // exclusiveBetween + //--------------------------------------------------------------------------------- + + /** + *

Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception.

+ * + *
Validate.inclusiveBetween(0, 2, 1);
+ * + * @param 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 void exclusiveBetween(T start, T end, Comparable value) { + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + /** + *

Validate that the specified argument object fall between the two + * exclusive values specified; otherwise, throws an exception with the + * specified message.

+ * + *
Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
+ * + * @param 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 void exclusiveBetween(T start, T end, Comparable value, String message, Object... values) { + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + // isInstanceOf + //--------------------------------------------------------------------------------- + + /** + *

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

+ * + *
Validate.isInstanceOf(OkClass.class, object);
+ * + *

The message of the exception is "The validated object is not an instance of" + * followed by the name of the class

+ * + * @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())); + } + } + + /** + *

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

+ * + *
Validate.isInstanceOf(OkClass.classs, object, "Wrong class, object is of class %s",
+     *   object.getClass().getName());
+ * + * @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 + //--------------------------------------------------------------------------------- + + /** + *

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.

+ * + *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ * + *

The message of the exception is "The validated object can not be converted to the" + * followed by the name of the class and "class"

+ * + * @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())); + } + } + + /** + *

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.

+ * + *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
+ * + *

The message of the exception is "The validated object can not be converted to the" + * followed by the name of the class and "class"

+ * + * @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; + +/** + *

+ * The Builder interface is designed to designate a class as a builder + * 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. + *

+ * + *

+ * 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. + *

+ * + *

+ * 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. + *

+ * + *

+ * Example Builder: + *

+ * class FontBuilder implements Builder<Font> {
+ *     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;
+ *     }
+ * }
+ * 
+ * + * Example Builder Usage: + *
+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ *                                                              .size(14.0f)
+ *                                                              .build();
+ * 
+ *

+ * + * @param 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 { + + /** + * 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 equals(Object) and + * hashcode() built with {@link EqualsBuilder} and + * {@link HashCodeBuilder}.

+ * + *

Two Objects that compare equal using equals(Object) should normally + * also compare equal using compareTo(Object).

+ * + *

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 compareTo(Object) and + * equals(Object).

+ * + *

To use this class write code as follows:

+ * + *
+ * 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();
+ *   }
+ * }
+ * 
+ * + *

Alternatively, there are {@link #reflectionCompare(Object, Object) reflectionCompare} methods that use + * reflection to determine the fields to append. Because fields can be private, + * reflectionCompare 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.

+ * + *

A typical implementation of compareTo(Object) using + * reflectionCompare looks like:

+ + *
+ * public int compareTo(Object o) {
+ *   return CompareToBuilder.reflectionCompare(this, o);
+ * }
+ * 
+ * + * @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 { + + /** + * Current state of the comparison as appended fields are checked. + */ + private int comparison; + + /** + *

Constructor for CompareToBuilder.

+ * + *

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.

+ */ + public CompareToBuilder() { + super(); + comparison = 0; + } + + //----------------------------------------------------------------------- + /** + *

Compares two Objects via reflection.

+ * + *

Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • Transient members will be not be compared, as they are likely derived + * fields
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both lhs and rhs are null, + * they are considered equal.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @return a negative integer, zero, or a positive integer as lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either (but not both) parameters are + * null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public static int reflectionCompare(Object lhs, Object rhs) { + return reflectionCompare(lhs, rhs, false, null); + } + + /** + *

Compares two Objects via reflection.

+ * + *

Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both lhs and rhs are null, + * they are considered equal.

+ * + * @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 lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public static int reflectionCompare(Object lhs, Object rhs, boolean compareTransients) { + return reflectionCompare(lhs, rhs, compareTransients, null); + } + + /** + *

Compares two Objects via reflection.

+ * + *

Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both lhs and rhs are null, + * they are considered equal.

+ * + * @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 lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 + */ + public static int reflectionCompare(Object lhs, Object rhs, Collection excludeFields) { + return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

Compares two Objects via reflection.

+ * + *

Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Superclass fields will be compared
  • + *
+ * + *

If both lhs and rhs are null, + * they are considered equal.

+ * + * @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 lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 + */ + public static int reflectionCompare(Object lhs, Object rhs, String... excludeFields) { + return reflectionCompare(lhs, rhs, false, null, excludeFields); + } + + /** + *

Compares two Objects via reflection.

+ * + *

Fields can be private, thus AccessibleObject.setAccessible + * is used to bypass normal access control checks. This will fail under a + * security manager unless the appropriate permissions are set.

+ * + *
    + *
  • Static fields will not be compared
  • + *
  • If the compareTransients is true, + * compares transient members. Otherwise ignores them, as they + * are likely derived fields.
  • + *
  • Compares superclass fields up to and including reflectUpToClass. + * If reflectUpToClass is null, compares all superclass fields.
  • + *
+ * + *

If both lhs and rhs are null, + * they are considered equal.

+ * + * @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 lhs + * is less than, equal to, or greater than rhs + * @throws NullPointerException if either lhs or rhs + * (but not both) is null + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @since 2.2 (2.0 as reflectionCompare(Object, Object, boolean, Class)) + */ + 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(); + } + + /** + *

Appends to builder the comparison of lhs + * to rhs using the fields defined in clazz.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param clazz Class that defines fields to be compared + * @param builder CompareToBuilder 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"); + } + } + } + } + + //----------------------------------------------------------------------- + /** + *

Appends to the builder the compareTo(Object) + * result of the superclass.

+ * + * @param superCompareTo result of calling super.compareTo(Object) + * @return this - used to chain append calls + * @since 2.0 + */ + public CompareToBuilder appendSuper(int superCompareTo) { + if (comparison != 0) { + return this; + } + comparison = superCompareTo; + return this; + } + + //----------------------------------------------------------------------- + /** + *

Appends to the builder the comparison of + * two Objects.

+ * + *
    + *
  1. Check if lhs == rhs
  2. + *
  3. Check if either lhs or rhs is null, + * a null object is less than a non-null object
  4. + *
  5. Check the object contents
  6. + *
+ * + *

lhs must either be an array or implement {@link Comparable}.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public CompareToBuilder append(Object lhs, Object rhs) { + return append(lhs, rhs, null); + } + + /** + *

Appends to the builder the comparison of + * two Objects.

+ * + *
    + *
  1. Check if lhs == rhs
  2. + *
  3. Check if either lhs or rhs is null, + * a null object is less than a non-null object
  4. + *
  5. Check the object contents
  6. + *
+ * + *

If lhs is an array, array comparison methods will be used. + * Otherwise comparator will be used to compare the objects. + * If comparator is null, lhs must + * implement {@link Comparable} instead.

+ * + * @param lhs left-hand object + * @param rhs right-hand object + * @param comparator Comparator used to compare the objects, + * null means treat lhs as Comparable + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @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 comparable = (Comparable) lhs; + comparison = comparable.compareTo(rhs); + } else { + @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc + final Comparator comparator2 = (Comparator) comparator; + comparison = comparator2.compare(lhs, rhs); + } + } + return this; + } + + //------------------------------------------------------------------------- + /** + * Appends to the builder the comparison of + * two longs. + * + * @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 builder the comparison of + * two ints. + * + * @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 builder the comparison of + * two shorts. + * + * @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 builder the comparison of + * two chars. + * + * @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 builder the comparison of + * two bytes. + * + * @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; + } + + /** + *

Appends to the builder the comparison of + * two doubles.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @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; + } + + /** + *

Appends to the builder the comparison of + * two floats.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @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 builder the comparison of + * two booleanss. + * + * @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; + } + + //----------------------------------------------------------------------- + /** + *

Appends to the builder the deep comparison of + * two Object arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a short length array is less than a long length array
  6. + *
  7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
  8. + *
+ * + *

This method will also will be called for the top level of multi-dimensional, + * ragged, and multi-typed arrays.

+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + */ + public CompareToBuilder append(Object[] lhs, Object[] rhs) { + return append(lhs, rhs, null); + } + + /** + *

Appends to the builder the deep comparison of + * two Object arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a short length array is less than a long length array
  6. + *
  7. Check array contents element by element using {@link #append(Object, Object, Comparator)}
  8. + *
+ * + *

This method will also will be called for the top level of multi-dimensional, + * ragged, and multi-typed arrays.

+ * + * @param lhs left-hand array + * @param rhs right-hand array + * @param comparator Comparator to use to compare the array elements, + * null means to treat lhs elements as Comparable. + * @return this - used to chain append calls + * @throws ClassCastException if rhs is not assignment-compatible + * with lhs + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two long arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(long, long)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two int arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(int, int)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two short arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(short, short)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two char arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(char, char)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two byte arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(byte, byte)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two double arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(double, double)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two float arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(float, float)}
  8. + *
+ * + * @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; + } + + /** + *

Appends to the builder the deep comparison of + * two boolean arrays.

+ * + *
    + *
  1. Check if arrays are the same using ==
  2. + *
  3. Check if for null, null is less than non-null
  4. + *
  5. Check array length, a shorter length array is less than a longer length array
  6. + *
  7. Check array contents element by element using {@link #append(boolean, boolean)}
  8. + *
+ * + * @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 builder 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 builder 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; + +/** + *

Assists in implementing {@link Object#equals(Object)} methods.

+ * + *

This class provides methods to build a good equals method for any + * class. It follows rules laid out in + * Effective Java + * , by Joshua Bloch. In particular the rule for comparing doubles, + * floats, and arrays can be tricky. Also, making sure that + * equals() and hashCode() are consistent can be + * difficult.

+ * + *

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.

+ * + *

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.

+ * + *

Typical use for the code is as follows:

+ *
+ * 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();
+ *  }
+ * 
+ * + *

Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * reflectionEquals, uses AccessibleObject.setAccessible 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.

+ * + *

A typical invocation for this method would look like:

+ *
+ * public boolean equals(Object obj) {
+ *   return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ * 
+ * + * @since 1.0 + * @version $Id: EqualsBuilder.java 1091531 2011-04-12 18:29:49Z ggregory $ + */ +public class EqualsBuilder implements Builder { + + /** + *

+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

+ * + * @since 3.0 + */ + private static final ThreadLocal>> REGISTRY = new ThreadLocal>>(); + + /* + * 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. + */ + + /** + *

+ * Returns the registry of object pairs being traversed by the reflection + * methods in the current thread. + *

+ * + * @return Set the registry of objects being traversed + * @since 3.0 + */ + static Set> getRegistry() { + return REGISTRY.get(); + } + + /** + *

+ * Converters value pair into a register pair. + *

+ * + * @param lhs this object + * @param rhs the other object + * + * @return the pair + */ + static Pair getRegisterPair(Object lhs, Object rhs) { + IDKey left = new IDKey(lhs); + IDKey right = new IDKey(rhs); + return Pair.of(left, right); + } + + /** + *

+ * Returns true 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. + *

+ * + * @param lhs this object to lookup in registry + * @param rhs the other object to lookup on registry + * @return boolean true if the registry contains the given object. + * @since 3.0 + */ + static boolean isRegistered(Object lhs, Object rhs) { + Set> registry = getRegistry(); + Pair pair = getRegisterPair(lhs, rhs); + Pair swappedPair = Pair.of(pair.getLeft(), pair.getRight()); + + return registry != null + && (registry.contains(pair) || registry.contains(swappedPair)); + } + + /** + *

+ * Registers the given object pair. + * Used by the reflection methods to avoid infinite loops. + *

+ * + * @param lhs this 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>()); + } + } + + Set> registry = getRegistry(); + Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } + + /** + *

+ * Unregisters the given object pair. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + * + * @param lhs this object to unregister + * @param rhs the other object to unregister + * @since 3.0 + */ + static void unregister(Object lhs, Object rhs) { + Set> registry = getRegistry(); + if (registry != null) { + Pair 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 true. + */ + private boolean isEquals = true; + + /** + *

Constructor for EqualsBuilder.

+ * + *

Starts off assuming that equals is true.

+ * @see Object#equals(Object) + */ + public EqualsBuilder() { + // do nothing for now. + } + + //------------------------------------------------------------------------- + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible 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.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param excludeFields Collection of String field names to exclude from testing + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs, Collection excludeFields) { + return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible 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.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs, String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible 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.

+ * + *

If the TestTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) { + return reflectionEquals(lhs, rhs, testTransients, null); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible 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.

+ * + *

If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

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.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @param excludeFields array of field names to exclude from testing + * @return true 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(); + } + + /** + *

Appends the fields and values defined by the given object of the + * given Class.

+ * + * @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); + } + } + + //------------------------------------------------------------------------- + + /** + *

Adds the result of super.equals() to this builder.

+ * + * @param superEquals the result of calling super.equals() + * @return EqualsBuilder - used to chain calls. + * @since 2.0 + */ + public EqualsBuilder appendSuper(boolean superEquals) { + if (isEquals == false) { + return this; + } + isEquals = superEquals; + return this; + } + + //------------------------------------------------------------------------- + + /** + *

Test if two Objects are equal using their + * equals method.

+ * + * @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; + } + + /** + *

+ * Test if two long s are equal. + *

+ * + * @param lhs + * the left hand long + * @param rhs + * the right hand long + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(long lhs, long rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two ints are equal.

+ * + * @param lhs the left hand int + * @param rhs the right hand int + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(int lhs, int rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two shorts are equal.

+ * + * @param lhs the left hand short + * @param rhs the right hand short + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(short lhs, short rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two chars are equal.

+ * + * @param lhs the left hand char + * @param rhs the right hand char + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(char lhs, char rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two bytes are equal.

+ * + * @param lhs the left hand byte + * @param rhs the right hand byte + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(byte lhs, byte rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two doubles are equal by testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand double + * @param rhs the right hand double + * @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)); + } + + /** + *

Test if two floats are equal byt testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand float + * @param rhs the right hand float + * @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)); + } + + /** + *

Test if two booleanss are equal.

+ * + * @param lhs the left hand boolean + * @param rhs the right hand boolean + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(boolean lhs, boolean rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Performs a deep comparison of two Object arrays.

+ * + *

This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

+ * + * @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; + } + 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; + } + + /** + *

Deep comparison of array of long. Length and all + * values are compared.

+ * + *

The method {@link #append(long, long)} is used.

+ * + * @param lhs the left hand long[] + * @param rhs the right hand long[] + * @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; + } + + /** + *

Deep comparison of array of int. Length and all + * values are compared.

+ * + *

The method {@link #append(int, int)} is used.

+ * + * @param lhs the left hand int[] + * @param rhs the right hand int[] + * @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; + } + + /** + *

Deep comparison of array of short. Length and all + * values are compared.

+ * + *

The method {@link #append(short, short)} is used.

+ * + * @param lhs the left hand short[] + * @param rhs the right hand short[] + * @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; + } + + /** + *

Deep comparison of array of char. Length and all + * values are compared.

+ * + *

The method {@link #append(char, char)} is used.

+ * + * @param lhs the left hand char[] + * @param rhs the right hand char[] + * @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; + } + + /** + *

Deep comparison of array of byte. Length and all + * values are compared.

+ * + *

The method {@link #append(byte, byte)} is used.

+ * + * @param lhs the left hand byte[] + * @param rhs the right hand byte[] + * @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; + } + + /** + *

Deep comparison of array of double. Length and all + * values are compared.

+ * + *

The method {@link #append(double, double)} is used.

+ * + * @param lhs the left hand double[] + * @param rhs the right hand double[] + * @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; + } + + /** + *

Deep comparison of array of float. Length and all + * values are compared.

+ * + *

The method {@link #append(float, float)} is used.

+ * + * @param lhs the left hand float[] + * @param rhs the right hand float[] + * @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; + } + + /** + *

Deep comparison of array of boolean. Length and all + * values are compared.

+ * + *

The method {@link #append(boolean, boolean)} is used.

+ * + * @param lhs the left hand boolean[] + * @param rhs the right hand boolean[] + * @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; + } + + /** + *

Returns true if the fields that have been checked + * are all equal.

+ * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } + + /** + *

Returns true if the fields that have been checked + * are all equal.

+ * + * @return true if all of the fields that have been checked + * are equal, false otherwise. + * + * @since 3.0 + */ + public Boolean build() { + return Boolean.valueOf(isEquals()); + } + + /** + * Sets the isEquals 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; + +/** + *

+ * Assists in implementing {@link Object#hashCode()} methods. + *

+ * + *

+ * This class enables a good hashCode method to be built for any class. It follows the rules laid out in + * the book Effective Java by Joshua Bloch. Writing a + * good hashCode method is actually quite difficult. This class aims to simplify the process. + *

+ * + *

+ * 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. + *

+ * + *

+ * All relevant fields from the object should be included in the hashCode method. Derived fields may be + * excluded. In general, any field used in the equals method must be used in the hashCode + * method. + *

+ * + *

+ * To use this class write code as follows: + *

+ * + *
+ * 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();
+ *   }
+ * }
+ * 
+ * + *

+ * If required, the superclass hashCode() can be added using {@link #appendSuper}. + *

+ * + *

+ * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are + * usually private, the method, reflectionHashCode, uses AccessibleObject.setAccessible + * 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. + *

+ * + *

+ * A typical invocation for this method would look like: + *

+ * + *
+ * public int hashCode() {
+ *   return HashCodeBuilder.reflectionHashCode(this);
+ * }
+ * 
+ * + * @since 1.0 + * @version $Id: HashCodeBuilder.java 1144929 2011-07-10 18:26:16Z ggregory $ + */ +public class HashCodeBuilder implements Builder { + /** + *

+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

+ * + * @since 2.3 + */ + private static final ThreadLocal> REGISTRY = new ThreadLocal>(); + + /* + * 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. + */ + + /** + *

+ * Returns the registry of objects being traversed by the reflection methods in the current thread. + *

+ * + * @return Set the registry of objects being traversed + * @since 2.3 + */ + static Set getRegistry() { + return REGISTRY.get(); + } + + /** + *

+ * Returns true if the registry contains the given object. Used by the reflection methods to avoid + * infinite loops. + *

+ * + * @param value + * The object to lookup in the registry. + * @return boolean true if the registry contains the given object. + * @since 2.3 + */ + static boolean isRegistered(Object value) { + Set registry = getRegistry(); + return registry != null && registry.contains(new IDKey(value)); + } + + /** + *

+ * Appends the fields and values defined by the given object of the given Class. + *

+ * + * @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); + } + } + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + *

+ * 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. + *

+ * + * @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 hashCode for + * @return int hash code + * @throws IllegalArgumentException + * if the Object is null + * @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); + } + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + *

+ * 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. + *

+ * + * @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 hashCode for + * @param testTransients + * whether to include transient fields + * @return int hash code + * @throws IllegalArgumentException + * if the Object is null + * @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); + } + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * 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. + *

+ * + *

+ * 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. + *

+ * + * @param + * 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 hashCode for + * @param testTransients + * whether to include transient fields + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @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 null + * @throws IllegalArgumentException + * if the number is zero or even + * @since 2.0 + */ + public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, T object, + boolean testTransients, Class 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(); + } + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the TestTransients parameter is set to true, transient members will be tested, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + * @param object + * the Object to create a hashCode for + * @param testTransients + * whether to include transient fields + * @return int hash code + * @throws IllegalArgumentException + * if the object is null + */ + public static int reflectionHashCode(Object object, boolean testTransients) { + return reflectionHashCode(17, 37, object, testTransients, null); + } + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + * @param object + * the Object to create a hashCode 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 null + */ + public static int reflectionHashCode(Object object, Collection excludeFields) { + return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + + // ------------------------------------------------------------------------- + + /** + *

+ * This method uses reflection to build a valid hash code. + *

+ * + *

+ * This constructor uses two hard coded choices for the constants needed to build a hash code. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the + * Object. + *

+ * + *

+ * Static fields will not be tested. Superclass fields will be included. + *

+ * + * @param object + * the Object to create a hashCode 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 null + */ + public static int reflectionHashCode(Object object, String... excludeFields) { + return reflectionHashCode(17, 37, object, false, null, excludeFields); + } + + /** + *

+ * Registers the given object. Used by the reflection methods to avoid infinite loops. + *

+ * + * @param value + * The object to register. + */ + static void register(Object value) { + synchronized (HashCodeBuilder.class) { + if (getRegistry() == null) { + REGISTRY.set(new HashSet()); + } + } + getRegistry().add(new IDKey(value)); + } + + /** + *

+ * Unregisters the given object. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + * + * @param value + * The object to unregister. + * @since 2.3 + */ + static void unregister(Object value) { + Set 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; + + /** + *

+ * Uses two hard coded choices for the constants needed to build a hashCode. + *

+ */ + public HashCodeBuilder() { + iConstant = 37; + iTotal = 17; + } + + /** + *

+ * 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. + *

+ * + * @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; + } + + /** + *

+ * Append a hashCode for a boolean. + *

+ *

+ * This adds 1 when true, and 0 when false to the hashCode. + *

+ *

+ * This is in contrast to the standard java.lang.Boolean.hashCode handling, which computes + * a hashCode value of 1231 for java.lang.Boolean instances + * that represent true or 1237 for java.lang.Boolean instances + * that represent false. + *

+ *

+ * This is in accordance with the Effective Java design. + *

+ * + * @param value + * the boolean to add to the hashCode + * @return this + */ + public HashCodeBuilder append(boolean value) { + iTotal = iTotal * iConstant + (value ? 0 : 1); + return this; + } + + /** + *

+ * Append a hashCode for a boolean array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(boolean[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (boolean element : array) { + append(element); + } + } + return this; + } + + // ------------------------------------------------------------------------- + + /** + *

+ * Append a hashCode for a byte. + *

+ * + * @param value + * the byte to add to the hashCode + * @return this + */ + public HashCodeBuilder append(byte value) { + iTotal = iTotal * iConstant + value; + return this; + } + + // ------------------------------------------------------------------------- + + /** + *

+ * Append a hashCode for a byte array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(byte[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (byte element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for a char. + *

+ * + * @param value + * the char to add to the hashCode + * @return this + */ + public HashCodeBuilder append(char value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

+ * Append a hashCode for a char array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(char[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (char element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for a double. + *

+ * + * @param value + * the double to add to the hashCode + * @return this + */ + public HashCodeBuilder append(double value) { + return append(Double.doubleToLongBits(value)); + } + + /** + *

+ * Append a hashCode for a double array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(double[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (double element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for a float. + *

+ * + * @param value + * the float to add to the hashCode + * @return this + */ + public HashCodeBuilder append(float value) { + iTotal = iTotal * iConstant + Float.floatToIntBits(value); + return this; + } + + /** + *

+ * Append a hashCode for a float array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(float[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (float element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for an int. + *

+ * + * @param value + * the int to add to the hashCode + * @return this + */ + public HashCodeBuilder append(int value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

+ * Append a hashCode for an int array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(int[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (int element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for a long. + *

+ * + * @param value + * the long to add to the hashCode + * @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; + } + + /** + *

+ * Append a hashCode for a long array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(long[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (long element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for an Object. + *

+ * + * @param object + * the Object to add to the hashCode + * @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; + } + + /** + *

+ * Append a hashCode for an Object array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(Object[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (Object element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Append a hashCode for a short. + *

+ * + * @param value + * the short to add to the hashCode + * @return this + */ + public HashCodeBuilder append(short value) { + iTotal = iTotal * iConstant + value; + return this; + } + + /** + *

+ * Append a hashCode for a short array. + *

+ * + * @param array + * the array to add to the hashCode + * @return this + */ + public HashCodeBuilder append(short[] array) { + if (array == null) { + iTotal = iTotal * iConstant; + } else { + for (short element : array) { + append(element); + } + } + return this; + } + + /** + *

+ * Adds the result of super.hashCode() to this builder. + *

+ * + * @param superHashCode + * the result of calling super.hashCode() + * @return this HashCodeBuilder, used to chain calls. + * @since 2.0 + */ + public HashCodeBuilder appendSuper(int superHashCode) { + iTotal = iTotal * iConstant + superHashCode; + return this; + } + + /** + *

+ * Return the computed hashCode. + *

+ * + * @return hashCode based on the fields appended + */ + public int toHashCode() { + return iTotal; + } + + /** + * Returns the computed hashCode. + * + * @return hashCode based on the fields appended + * + * @since 3.0 + */ + public Integer build() { + return Integer.valueOf(toHashCode()); + } + + /** + *

+ * The computed hashCode 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.

+ * + * @return hashCode 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; + +/** + *

+ * Assists in implementing {@link Object#toString()} methods using reflection. + *

+ * + *

+ * 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. + *

+ * + *

+ * A typical invocation for this method would look like: + *

+ * + *
+ * public String toString() {
+ *   return ReflectionToStringBuilder.toString(this);
+ * }
+ * + * + * + *

+ * You can also use the builder to debug 3rd party objects: + *

+ * + *
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
+ * + * + * + *

+ * A subclass can control field output by overriding the methods: + *

    + *
  • {@link #accept(java.lang.reflect.Field)}
  • + *
  • {@link #getValue(java.lang.reflect.Field)}
  • + *
+ *

+ *

+ * For example, this method does not include the password field in the returned + * String: + *

+ * + *
+ * public String toString() {
+ *     return (new ReflectionToStringBuilder(this) {
+ *         protected boolean accept(Field f) {
+ *             return super.accept(f) && !f.getName().equals("password");
+ *         }
+ *     }).toString();
+ * }
+ * + * + * + *

+ * The exact format of the toString is determined by the {@link ToStringStyle} passed into the + * constructor. + *

+ * + * @since 2.0 + * @version $Id: ReflectionToStringBuilder.java 1090821 2011-04-10 15:59:07Z mbenson $ + */ +public class ReflectionToStringBuilder extends ToStringBuilder { + + /** + *

+ * Builds a toString value using the default ToStringStyle through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

+ * + * @param object + * the Object to be output + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + */ + public static String toString(Object object) { + return toString(object, null, false, false, null); + } + + /** + *

+ * Builds a toString value through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * Transient members will be not be included, as they are likely derived. Static fields will not be included. + * Superclass fields will be appended. + *

+ * + *

+ * If the style is null, the default ToStringStyle is used. + *

+ * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @return the String result + * @throws IllegalArgumentException + * if the Object or ToStringStyle is null + */ + public static String toString(Object object, ToStringStyle style) { + return toString(object, style, false, false, null); + } + + /** + *

+ * Builds a toString value through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the outputTransients is true, transient members will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * Static fields will not be included. Superclass fields will be appended. + *

+ * + *

+ * If the style is null, the default ToStringStyle is used. + *

+ * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @param outputTransients + * whether to include transient fields + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + */ + public static String toString(Object object, ToStringStyle style, boolean outputTransients) { + return toString(object, style, outputTransients, false, null); + } + + /** + *

+ * Builds a toString value through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the outputTransients is true, transient fields will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * If the outputStatics is true, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * Static fields will not be included. Superclass fields will be appended. + *

+ * + *

+ * If the style is null, the default ToStringStyle is used. + *

+ * + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include transient fields + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + * @since 2.1 + */ + public static String toString(Object object, ToStringStyle style, boolean outputTransients, boolean outputStatics) { + return toString(object, style, outputTransients, outputStatics, null); + } + + /** + *

+ * Builds a toString value through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible 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. + *

+ * + *

+ * If the outputTransients is true, transient fields will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * If the outputStatics is true, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as + * java.lang.Object. + *

+ * + *

+ * If the style is null, the default ToStringStyle is used. + *

+ * + * @param + * the type of the object + * @param object + * the Object to be output + * @param style + * the style of the toString to create, may be null + * @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 null + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + * @since 2.1 + */ + public static String toString( + T object, ToStringStyle style, boolean outputTransients, + boolean outputStatics, Class 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 excludeFieldNames) { + return toStringExclude(object, toNoNullStringArray(excludeFieldNames)); + } + + /** + * Converts the given Collection into an array of Strings. The returned array does not contain null + * entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element + * is null. + * + * @param collection + * The collection to convert + * @return A new array of Strings. + */ + static String[] toNoNullStringArray(Collection 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 null. + * + * @param array + * The array to check + * @return The given array or a new array without null. + */ + static String[] toNoNullStringArray(Object[] array) { + List list = new ArrayList(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 "password". + * + * @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; + + /** + *

+ * Constructor. + *

+ * + *

+ * This constructor outputs using the default style set with setDefaultStyle. + *

+ * + * @param object + * the Object to build a toString for, must not be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(Object object) { + super(object); + } + + /** + *

+ * Constructor. + *

+ * + *

+ * If the style is null, the default style is used. + *

+ * + * @param object + * the Object to build a toString for, must not be null + * @param style + * the style of the toString to create, may be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(Object object, ToStringStyle style) { + super(object, style); + } + + /** + *

+ * Constructor. + *

+ * + *

+ * If the style is null, the default style is used. + *

+ * + *

+ * If the buffer is null, a new one is created. + *

+ * + * @param object + * the Object to build a toString for + * @param style + * the style of the toString to create, may be null + * @param buffer + * the StringBuffer to populate, may be null + * @throws IllegalArgumentException + * if the Object passed in is null + */ + public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) { + super(object, style, buffer); + } + + /** + * Constructor. + * + * @param + * the type of the object + * @param object + * the Object to build a toString for + * @param style + * the style of the toString to create, may be null + * @param buffer + * the StringBuffer to populate, may be null + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include static fields + * @since 2.1 + */ + public ReflectionToStringBuilder( + T object, ToStringStyle style, StringBuffer buffer, + Class 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 Field. + *
    + *
  • Transient fields are appended only if {@link #isAppendTransients()} returns true. + *
  • Static fields are appended only if {@link #isAppendStatics()} returns true. + *
  • Inner class fields are not appened.
  • + *
+ * + * @param field + * The Field to test. + * @return Whether or not to append the given Field. + */ + 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; + } + + /** + *

+ * Appends the fields and values defined by the given object of the given Class. + *

+ * + *

+ * If a cycle is detected as an object is "toString()'ed", such an object is rendered as if + * Object.toString() had been called and not implemented by the object. + *

+ * + * @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(); + } + + /** + *

+ * Gets the last super class to stop appending fields for. + *

+ * + * @return The last super class to stop appending fields for. + */ + public Class getUpToClass() { + return this.upToClass; + } + + /** + *

+ * Calls java.lang.reflect.Field.get(Object). + *

+ * + * @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()); + } + + /** + *

+ * Gets whether or not to append static fields. + *

+ * + * @return Whether or not to append static fields. + * @since 2.1 + */ + public boolean isAppendStatics() { + return this.appendStatics; + } + + /** + *

+ * Gets whether or not to append transient fields. + *

+ * + * @return Whether or not to append transient fields. + */ + public boolean isAppendTransients() { + return this.appendTransients; + } + + /** + *

+ * Append to the toString an Object array. + *

+ * + * @param array + * the array to add to the toString + * @return this + */ + public ReflectionToStringBuilder reflectionAppendArray(Object array) { + this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array); + return this; + } + + /** + *

+ * Sets whether or not to append static fields. + *

+ * + * @param appendStatics + * Whether or not to append static fields. + * @since 2.1 + */ + public void setAppendStatics(boolean appendStatics) { + this.appendStatics = appendStatics; + } + + /** + *

+ * Sets whether or not to append transient fields. + *

+ * + * @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 null. + * @return this + */ + 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; + } + + /** + *

+ * Sets the last super class to stop appending fields for. + *

+ * + * @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; + } + + /** + *

+ * Gets the String built by this builder. + *

+ * + * @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; + +/** + *

Works with {@link ToStringBuilder} to create a toString.

+ * + *

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.

+ * + * @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; + + /** + *

Constructor.

+ */ + public StandardToStringStyle() { + super(); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the class name.

+ * + * @return the current useClassName flag + */ + @Override + public boolean isUseClassName() { // NOPMD as this is implementing the abstract class + return super.isUseClassName(); + } + + /** + *

Sets whether to use the class name.

+ * + * @param useClassName the new useClassName flag + */ + @Override + public void setUseClassName(boolean useClassName) { // NOPMD as this is implementing the abstract class + super.setUseClassName(useClassName); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output short or long class names.

+ * + * @return the current useShortClassName flag + * @since 2.0 + */ + @Override + public boolean isUseShortClassName() { // NOPMD as this is implementing the abstract class + return super.isUseShortClassName(); + } + + /** + *

Sets whether to output short or long class names.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the identity hash code.

+ * @return the current useIdentityHashCode flag + */ + @Override + public boolean isUseIdentityHashCode() { // NOPMD as this is implementing the abstract class + return super.isUseIdentityHashCode(); + } + + /** + *

Sets whether to use the identity hash code.

+ * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + @Override + public void setUseIdentityHashCode(boolean useIdentityHashCode) { // NOPMD as this is implementing the abstract class + super.setUseIdentityHashCode(useIdentityHashCode); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the field names passed in.

+ * + * @return the current useFieldNames flag + */ + @Override + public boolean isUseFieldNames() { // NOPMD as this is implementing the abstract class + return super.isUseFieldNames(); + } + + /** + *

Sets whether to use the field names passed in.

+ * + * @param useFieldNames the new useFieldNames flag + */ + @Override + public void setUseFieldNames(boolean useFieldNames) { // NOPMD as this is implementing the abstract class + super.setUseFieldNames(useFieldNames); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use full detail when the caller doesn't + * specify.

+ * + * @return the current defaultFullDetail flag + */ + @Override + public boolean isDefaultFullDetail() { // NOPMD as this is implementing the abstract class + return super.isDefaultFullDetail(); + } + + /** + *

Sets whether to use full detail when the caller doesn't + * specify.

+ * + * @param defaultFullDetail the new defaultFullDetail flag + */ + @Override + public void setDefaultFullDetail(boolean defaultFullDetail) { // NOPMD as this is implementing the abstract class + super.setDefaultFullDetail(defaultFullDetail); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output array content detail.

+ * + * @return the current array content detail setting + */ + @Override + public boolean isArrayContentDetail() { // NOPMD as this is implementing the abstract class + return super.isArrayContentDetail(); + } + + /** + *

Sets whether to output array content detail.

+ * + * @param arrayContentDetail the new arrayContentDetail flag + */ + @Override + public void setArrayContentDetail(boolean arrayContentDetail) { // NOPMD as this is implementing the abstract class + super.setArrayContentDetail(arrayContentDetail); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array start text.

+ * + * @return the current array start text + */ + @Override + public String getArrayStart() { // NOPMD as this is implementing the abstract class + return super.getArrayStart(); + } + + /** + *

Sets the array start text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param arrayStart the new array start text + */ + @Override + public void setArrayStart(String arrayStart) { // NOPMD as this is implementing the abstract class + super.setArrayStart(arrayStart); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array end text.

+ * + * @return the current array end text + */ + @Override + public String getArrayEnd() { // NOPMD as this is implementing the abstract class + return super.getArrayEnd(); + } + + /** + *

Sets the array end text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param arrayEnd the new array end text + */ + @Override + public void setArrayEnd(String arrayEnd) { // NOPMD as this is implementing the abstract class + super.setArrayEnd(arrayEnd); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array separator text.

+ * + * @return the current array separator text + */ + @Override + public String getArraySeparator() { // NOPMD as this is implementing the abstract class + return super.getArraySeparator(); + } + + /** + *

Sets the array separator text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param arraySeparator the new array separator text + */ + @Override + public void setArraySeparator(String arraySeparator) { // NOPMD as this is implementing the abstract class + super.setArraySeparator(arraySeparator); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content start text.

+ * + * @return the current content start text + */ + @Override + public String getContentStart() { // NOPMD as this is implementing the abstract class + return super.getContentStart(); + } + + /** + *

Sets the content start text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param contentStart the new content start text + */ + @Override + public void setContentStart(String contentStart) { // NOPMD as this is implementing the abstract class + super.setContentStart(contentStart); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content end text.

+ * + * @return the current content end text + */ + @Override + public String getContentEnd() { // NOPMD as this is implementing the abstract class + return super.getContentEnd(); + } + + /** + *

Sets the content end text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param contentEnd the new content end text + */ + @Override + public void setContentEnd(String contentEnd) { // NOPMD as this is implementing the abstract class + super.setContentEnd(contentEnd); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field name value separator text.

+ * + * @return the current field name value separator text + */ + @Override + public String getFieldNameValueSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldNameValueSeparator(); + } + + /** + *

Sets the field name value separator text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field separator text.

+ * + * @return the current field separator text + */ + @Override + public String getFieldSeparator() { // NOPMD as this is implementing the abstract class + return super.getFieldSeparator(); + } + + /** + *

Sets the field separator text.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param fieldSeparator the new field separator text + */ + @Override + public void setFieldSeparator(String fieldSeparator) { // NOPMD as this is implementing the abstract class + super.setFieldSeparator(fieldSeparator); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the start + * of each buffer.

+ * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtStart() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtStart(); + } + + /** + *

Sets whether the field separator should be added at the start + * of each buffer.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the end + * of each buffer.

+ * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtEnd() { // NOPMD as this is implementing the abstract class + return super.isFieldSeparatorAtEnd(); + } + + /** + *

Sets whether the field separator should be added at the end + * of each buffer.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the text to output when null found.

+ * + * @return the current text to output when null found + */ + @Override + public String getNullText() { // NOPMD as this is implementing the abstract class + return super.getNullText(); + } + + /** + *

Sets the text to output when null found.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @param nullText the new text to output when null found + */ + @Override + public void setNullText(String nullText) { // NOPMD as this is implementing the abstract class + super.setNullText(nullText); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the text to output when a Collection, + * Map or Array size is output.

+ * + *

This is output before the size value.

+ * + * @return the current start of size text + */ + @Override + public String getSizeStartText() { // NOPMD as this is implementing the abstract class + return super.getSizeStartText(); + } + + /** + *

Sets the start text to output when a Collection, + * Map or Array size is output.

+ * + *

This is output before the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @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 Collection, + * Map or Array size is output.

+ * + *

This is output after the size value.

+ * + * @return the current end of size text + */ + @Override + public String getSizeEndText() { // NOPMD as this is implementing the abstract class + return super.getSizeEndText(); + } + + /** + *

Sets the end text to output when a Collection, + * Map or Array size is output.

+ * + *

This is output after the size value.

+ * + *

null is accepted, but will be converted + * to an empty String.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when an Object is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + * @return the current start of summary text + */ + @Override + public String getSummaryObjectStartText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectStartText(); + } + + /** + *

Sets the start text to output when an Object is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @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); + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when an Object is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + * @return the current end of summary text + */ + @Override + public String getSummaryObjectEndText() { // NOPMD as this is implementing the abstract class + return super.getSummaryObjectEndText(); + } + + /** + *

Sets the end text to output when an Object is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @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; + +/** + *

Assists in implementing {@link Object#toString()} methods.

+ * + *

This class enables a good and consistent toString() to be built for any + * class or object. This class aims to simplify the process by:

+ *
    + *
  • allowing field names
  • + *
  • handling all types consistently
  • + *
  • handling nulls consistently
  • + *
  • outputting arrays and multi-dimensional arrays
  • + *
  • enabling the detail level to be controlled for Objects and Collections
  • + *
  • handling class hierarchies
  • + *
+ * + *

To use this class write code as follows:

+ * + *
+ * 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();
+ *   }
+ * }
+ * 
+ * + *

This will produce a toString of the format: + * Person@7f54[name=Stephen,age=29,smoker=false]

+ * + *

To add the superclass toString, use {@link #appendSuper}. + * To append the toString from an object that is delegated + * to (or any other object), use {@link #appendToString}.

+ * + *

Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * reflectionToString, uses AccessibleObject.setAccessible 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.

+ * + *

A typical invocation for this method would look like:

+ * + *
+ * public String toString() {
+ *   return ToStringBuilder.reflectionToString(this);
+ * }
+ * 
+ * + *

You can also use the builder to debug 3rd party objects:

+ * + *
+ * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
+ * 
+ * + *

The exact format of the toString is determined by + * the {@link ToStringStyle} passed into the constructor.

+ * + * @since 1.0 + * @version $Id: ToStringBuilder.java 1088899 2011-04-05 05:31:27Z bayard $ + */ +public class ToStringBuilder implements Builder { + + /** + * The default style of output to use, not null. + */ + private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE; + + //---------------------------------------------------------------------------- + + /** + *

Gets the default ToStringStyle to use.

+ * + *

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 ToStringStyle to the constructor instead + * of using this global default.

+ * + *

This method can be used from multiple threads. + * Internally, a volatile 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.

+ * + *

One reason for changing the default could be to have a verbose style during + * development and a compact style in production.

+ * + * @return the default ToStringStyle, never null + */ + public static ToStringStyle getDefaultStyle() { + return defaultStyle; + } + + /** + *

Sets the default ToStringStyle to use.

+ * + *

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 ToStringStyle to the constructor instead + * of changing this global default.

+ * + *

This method is not intended for use from multiple threads. + * Internally, a volatile variable is used to provide the guarantee + * that the latest value set is the value returned from {@link #getDefaultStyle}.

+ * + * @param style the default ToStringStyle + * @throws IllegalArgumentException if the style is null + */ + public static void setDefaultStyle(ToStringStyle style) { + if (style == null) { + throw new IllegalArgumentException("The style must not be null"); + } + defaultStyle = style; + } + + //---------------------------------------------------------------------------- + /** + *

Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

+ * + * @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); + } + + /** + *

Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

+ * + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @return the String result + * @see ReflectionToStringBuilder#toString(Object,ToStringStyle) + */ + public static String reflectionToString(Object object, ToStringStyle style) { + return ReflectionToStringBuilder.toString(object, style); + } + + /** + *

Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

+ * + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @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); + } + + /** + *

Uses ReflectionToStringBuilder to generate a + * toString for the specified object.

+ * + * @param the type of the object + * @param object the Object to be output + * @param style the style of the toString to create, may be null + * @param outputTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), may be null + * @return the String result + * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,boolean,Class) + * @since 2.0 + */ + public static String reflectionToString( + T object, + ToStringStyle style, + boolean outputTransients, + Class 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; + + /** + *

Constructs a builder for the specified object using the default output style.

+ * + *

This default style is obtained from {@link #getDefaultStyle()}.

+ * + * @param object the Object to build a toString for, not recommended to be null + */ + public ToStringBuilder(Object object) { + this(object, null, null); + } + + /** + *

Constructs a builder for the specified object using the a defined output style.

+ * + *

If the style is null, the default style is used.

+ * + * @param object the Object to build a toString for, not recommended to be null + * @param style the style of the toString to create, null uses the default style + */ + public ToStringBuilder(Object object, ToStringStyle style) { + this(object, style, null); + } + + /** + *

Constructs a builder for the specified object.

+ * + *

If the style is null, the default style is used.

+ * + *

If the buffer is null, a new one is created.

+ * + * @param object the Object to build a toString for, not recommended to be null + * @param style the style of the toString to create, null uses the default style + * @param buffer the StringBuffer 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a boolean + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(boolean value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a boolean + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(boolean[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a byte + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(byte value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a byte + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(byte[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a char + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(char value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a char + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(char[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a double + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(double value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a double + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(double[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a float + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(float value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a float + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(float[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an int + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(int value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an int + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(int[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a long + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(long value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a long + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(long[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an Object + * value.

+ * + * @param obj the value to add to the toString + * @return this + */ + public ToStringBuilder append(Object obj) { + style.append(buffer, null, obj, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an Object + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(Object[] array) { + style.append(buffer, null, array, null); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a short + * value.

+ * + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(short value) { + style.append(buffer, null, value); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a short + * array.

+ * + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(short[] array) { + style.append(buffer, null, array, null); + return this; + } + + /** + *

Append to the toString a boolean + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, boolean value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a boolean + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the hashCode + * @return this + */ + public ToStringBuilder append(String fieldName, boolean[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a boolean + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, boolean[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an byte + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, byte value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a byte array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, byte[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a byte + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array. + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, byte[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString a char + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, char value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a char + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, char[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a char + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, char[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString a double + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, double value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a double + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, double[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a double + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, double[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an float + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, float value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a float + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, float[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a float + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, float[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an int + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, int value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString an int + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, int[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString an int + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, int[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString a long + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, long value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a long + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, long[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a long + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, long[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an Object + * value.

+ * + * @param fieldName the field name + * @param obj the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, Object obj) { + style.append(buffer, fieldName, obj, null); + return this; + } + + /** + *

Append to the toString an Object + * value.

+ * + * @param fieldName the field name + * @param obj the value to add to the toString + * @param fullDetail true for detail, + * false for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, Object obj, boolean fullDetail) { + style.append(buffer, fieldName, obj, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an Object + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, Object[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString an Object + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, Object[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Append to the toString an short + * value.

+ * + * @param fieldName the field name + * @param value the value to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, short value) { + style.append(buffer, fieldName, value); + return this; + } + + /** + *

Append to the toString a short + * array.

+ * + * @param fieldName the field name + * @param array the array to add to the toString + * @return this + */ + public ToStringBuilder append(String fieldName, short[] array) { + style.append(buffer, fieldName, array, null); + return this; + } + + /** + *

Append to the toString a short + * array.

+ * + *

A boolean parameter controls the level of detail to show. + * Setting true will output the array in full. Setting + * false will output a summary, typically the size of + * the array. + * + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info + * @return this + */ + public ToStringBuilder append(String fieldName, short[] array, boolean fullDetail) { + style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail)); + return this; + } + + /** + *

Appends with the same format as the default Object toString() + * method. Appends the class name followed by + * {@link System#identityHashCode(java.lang.Object)}.

+ * + * @param object the Object whose class name and id to output + * @return this + * @since 2.0 + */ + public ToStringBuilder appendAsObjectToString(Object object) { + ObjectUtils.identityToString(this.getStringBuffer(), object); + return this; + } + + //---------------------------------------------------------------------------- + + /** + *

Append the toString from the superclass.

+ * + *

This method assumes that the superclass uses the same ToStringStyle + * as this one.

+ * + *

If superToString is null, no change is made.

+ * + * @param superToString the result of super.toString() + * @return this + * @since 2.0 + */ + public ToStringBuilder appendSuper(String superToString) { + if (superToString != null) { + style.appendSuper(buffer, superToString); + } + return this; + } + + /** + *

Append the toString from another object.

+ * + *

This method is useful where a class delegates most of the implementation of + * its properties to another class. You can then call toString() on + * the other class and pass the result into this method.

+ * + *
+     *   private AnotherObject delegate;
+     *   private String fieldInThisClass;
+     *
+     *   public String toString() {
+     *     return new ToStringBuilder(this).
+     *       appendToString(delegate.toString()).
+     *       append(fieldInThisClass).
+     *       toString();
+     *   }
+ * + *

This method assumes that the other object uses the same ToStringStyle + * as this one.

+ * + *

If the toString is null, no change is made.

+ * + * @param toString the result of toString() on another object + * @return this + * @since 2.0 + */ + public ToStringBuilder appendToString(String toString) { + if (toString != null) { + style.appendToString(buffer, toString); + } + return this; + } + + /** + *

Returns the Object being output.

+ * + * @return The object being output. + * @since 2.0 + */ + public Object getObject() { + return object; + } + + /** + *

Gets the StringBuffer being populated.

+ * + * @return the StringBuffer being populated + */ + public StringBuffer getStringBuffer() { + return buffer; + } + + //---------------------------------------------------------------------------- + + /** + *

Gets the ToStringStyle being used.

+ * + * @return the ToStringStyle being used + * @since 2.0 + */ + public ToStringStyle getStyle() { + return style; + } + + /** + *

Returns the built toString.

+ * + *

This method appends the end of data indicator, and can only be called once. + * Use {@link #getStringBuffer} to get the current string state.

+ * + *

If the object is null, return the style's nullText

+ * + * @return the String toString + */ + @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 toString + * + * @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; + +/** + *

Controls String formatting for {@link ToStringBuilder}. + * The main public interface is always via ToStringBuilder.

+ * + *

These classes are intended to be used as Singletons. + * 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.

+ * + *

If required, a subclass can override as many or as few of the + * methods as it requires. Each object type (from boolean + * to long to Object to int[]) has + * its own methods to output it. Most have two versions, detail and summary. + * + *

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.

+ * + *

If you want to format the output of certain objects, such as dates, you + * must create a subclass and override a method. + *

+ * 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);
+ *   }
+ * }
+ * 
+ *

+ * + * @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 Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
+     * Person@182f0db[name=John Doe,age=33,smoker=false]
+     * 
+ */ + public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle(); + + /** + * The multi line toString style. Using the Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
+     * Person@182f0db[
+     *   name=John Doe
+     *   age=33
+     *   smoker=false
+     * ]
+     * 
+ */ + public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle(); + + /** + * The no field names toString style. Using the Using the + * Person example from {@link ToStringBuilder}, the output + * would look like this: + * + *
+     * Person@182f0db[John Doe,33,false]
+     * 
+ */ + public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle(); + + /** + * The short prefix toString style. Using the Person example + * from {@link ToStringBuilder}, the output would look like this: + * + *
+     * Person[name=John Doe,age=33,smoker=false]
+     * 
+ * + * @since 2.1 + */ + public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle(); + + /** + * The simple toString style. Using the Using the Person + * example from {@link ToStringBuilder}, the output would look like this: + * + *
+     * John Doe,33,false
+     * 
+ */ + public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle(); + + /** + *

+ * A registry of objects used by reflectionToString methods + * to detect cyclical object references and avoid infinite loops. + *

+ */ + private static final ThreadLocal> REGISTRY = + new ThreadLocal>(); + + /** + *

+ * Returns the registry of objects being traversed by the reflectionToString + * methods in the current thread. + *

+ * + * @return Set the registry of objects being traversed + */ + static Map getRegistry() { + return REGISTRY.get(); + } + + /** + *

+ * Returns true if the registry contains the given object. + * Used by the reflection methods to avoid infinite loops. + *

+ * + * @param value + * The object to lookup in the registry. + * @return boolean true if the registry contains the given + * object. + */ + static boolean isRegistered(Object value) { + Map m = getRegistry(); + return m != null && m.containsKey(value); + } + + /** + *

+ * Registers the given object. Used by the reflection methods to avoid + * infinite loops. + *

+ * + * @param value + * The object to register. + */ + static void register(Object value) { + if (value != null) { + Map m = getRegistry(); + if (m == null) { + REGISTRY.set(new WeakHashMap()); + } + getRegistry().put(value, null); + } + } + + /** + *

+ * Unregisters the given object. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + *

+ * + * @param value + * The object to unregister. + */ + static void unregister(Object value) { + if (value != null) { + Map m = getRegistry(); + if (m != null) { + m.remove(value); + if (m.isEmpty()) { + REGISTRY.remove(); + } + } + } + } + + /** + * Whether to use the field names, the default is true. + */ + private boolean useFieldNames = true; + + /** + * Whether to use the class name, the default is true. + */ + private boolean useClassName = true; + + /** + * Whether to use short class names, the default is false. + */ + private boolean useShortClassName = false; + + /** + * Whether to use the identity hash code, the default is true. + */ + private boolean useIdentityHashCode = true; + + /** + * The content start '['. + */ + private String contentStart = "["; + + /** + * The content end ']'. + */ + private String contentEnd = "]"; + + /** + * The field name value separator '='. + */ + 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 ','. + */ + private String fieldSeparator = ","; + + /** + * The array start '{'. + */ + private String arrayStart = "{"; + + /** + * The array separator ','. + */ + private String arraySeparator = ","; + + /** + * The detail for array content. + */ + private boolean arrayContentDetail = true; + + /** + * The array end '}'. + */ + private String arrayEnd = "}"; + + /** + * The value to use when fullDetail is null, + * the default value is true. + */ + private boolean defaultFullDetail = true; + + /** + * The null text '<null>'. + */ + private String nullText = ""; + + /** + * The summary size text start '. + */ + private String sizeStartText = "'>'. + */ + private String sizeEndText = ">"; + + /** + * The summary object text start '<'. + */ + private String summaryObjectStartText = "<"; + + /** + * The summary object text start '>'. + */ + private String summaryObjectEndText = ">"; + + //---------------------------------------------------------------------------- + + /** + *

Constructor.

+ */ + protected ToStringStyle() { + super(); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString the superclass toString.

+ *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

+ * + *

A null superToString is ignored.

+ * + * @param buffer the StringBuffer to populate + * @param superToString the super.toString() + * @since 2.0 + */ + public void appendSuper(StringBuffer buffer, String superToString) { + appendToString(buffer, superToString); + } + + /** + *

Append to the toString another toString.

+ *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

+ * + *

A null toString is ignored.

+ * + * @param buffer the StringBuffer to populate + * @param toString the additional toString + * @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); + } + } + } + + /** + *

Append to the toString the start of data indicator.

+ * + * @param buffer the StringBuffer to populate + * @param object the Object to build a toString for + */ + public void appendStart(StringBuffer buffer, Object object) { + if (object != null) { + appendClassName(buffer, object); + appendIdentityHashCode(buffer, object); + appendContentStart(buffer); + if (fieldSeparatorAtStart) { + appendFieldSeparator(buffer); + } + } + } + + /** + *

Append to the toString the end of data indicator.

+ * + * @param buffer the StringBuffer to populate + * @param object the Object to build a + * toString for. + */ + public void appendEnd(StringBuffer buffer, Object object) { + if (this.fieldSeparatorAtEnd == false) { + removeLastFieldSeparator(buffer); + } + appendContentEnd(buffer); + unregister(object); + } + + /** + *

Remove the last field separator from the buffer.

+ * + * @param buffer the StringBuffer 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); + } + } + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an Object + * value, printing the full toString of the + * Object passed in.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString an Object, + * correctly interpreting its type.

+ * + *

This method performs the main lookup by Class type to correctly + * route arrays, Collections, Maps and + * Objects to the appropriate method.

+ * + *

Either detail or summary views can be specified.

+ * + *

If a cycle is detected, an object will be appended with the + * Object.toString() format.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + * @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); + } + } + + /** + *

Append to the toString an Object + * value that has been detected to participate in a cycle. This + * implementation will print the standard string value of the value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + * + * @since 2.2 + */ + protected void appendCyclicObject(StringBuffer buffer, String fieldName, Object value) { + ObjectUtils.identityToString(buffer, value); + } + + /** + *

Append to the toString an Object + * value, printing the full detail of the Object.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + */ + protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { + buffer.append(value); + } + + /** + *

Append to the toString a Collection.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param coll the Collection to add to the + * toString, not null + */ + protected void appendDetail(StringBuffer buffer, String fieldName, Collection coll) { + buffer.append(coll); + } + + /** + *

Append to the toString a Map.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param map the Map to add to the toString, + * not null + */ + protected void appendDetail(StringBuffer buffer, String fieldName, Map map) { + buffer.append(map); + } + + /** + *

Append to the toString an Object + * value, printing a summary of the Object.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, Object value) { + buffer.append(summaryObjectStartText); + buffer.append(getShortClassName(value.getClass())); + buffer.append(summaryObjectEndText); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a long + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, long value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a long + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, long value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an int + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, int value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString an int + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, int value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a short + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, short value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a short + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, short value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a byte + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, byte value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a byte + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, byte value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a char + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, char value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a char + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, char value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a double + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, double value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a double + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, double value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a float + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, float value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a float + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, float value) { + buffer.append(value); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a boolean + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param value the value to add to the toString + */ + public void append(StringBuffer buffer, String fieldName, boolean value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + *

Append to the toString a boolean + * value.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the toString + */ + protected void appendDetail(StringBuffer buffer, String fieldName, boolean value) { + buffer.append(value); + } + + /** + *

Append to the toString an Object + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString the detail of an + * Object array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString the detail of an array type.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + * @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); + } + + /** + *

Append to the toString a summary of an + * Object array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, Object[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a long + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * long array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * long array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, long[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString an int + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of an + * int array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of an + * int array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, int[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a short + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * short array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * short array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, short[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a byte + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * byte array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * byte array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, byte[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a char + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * char array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * char array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, char[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a double + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * double array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * double array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, double[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a float + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * float array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * float array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, float[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString a boolean + * array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail true for detail, false + * for summary info, null 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); + } + + /** + *

Append to the toString the detail of a + * boolean array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + 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); + } + + /** + *

Append to the toString a summary of a + * boolean array.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the toString, + * not null + */ + protected void appendSummary(StringBuffer buffer, String fieldName, boolean[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + //---------------------------------------------------------------------------- + + /** + *

Append to the toString the class name.

+ * + * @param buffer the StringBuffer to populate + * @param object the Object 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()); + } + } + } + + /** + *

Append the {@link System#identityHashCode(java.lang.Object)}.

+ * + * @param buffer the StringBuffer to populate + * @param object the Object 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))); + } + } + + /** + *

Append to the toString the content start.

+ * + * @param buffer the StringBuffer to populate + */ + protected void appendContentStart(StringBuffer buffer) { + buffer.append(contentStart); + } + + /** + *

Append to the toString the content end.

+ * + * @param buffer the StringBuffer to populate + */ + protected void appendContentEnd(StringBuffer buffer) { + buffer.append(contentEnd); + } + + /** + *

Append to the toString an indicator for null.

+ * + *

The default indicator is '<null>'.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendNullText(StringBuffer buffer, String fieldName) { + buffer.append(nullText); + } + + /** + *

Append to the toString the field separator.

+ * + * @param buffer the StringBuffer to populate + */ + protected void appendFieldSeparator(StringBuffer buffer) { + buffer.append(fieldSeparator); + } + + /** + *

Append to the toString the field start.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name + */ + protected void appendFieldStart(StringBuffer buffer, String fieldName) { + if (useFieldNames && fieldName != null) { + buffer.append(fieldName); + buffer.append(fieldNameValueSeparator); + } + } + + /** + *

Append to the toString the field end.

+ * + * @param buffer the StringBuffer to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendFieldEnd(StringBuffer buffer, String fieldName) { + appendFieldSeparator(buffer); + } + + /** + *

Append to the toString a size summary.

+ * + *

The size summary is used to summarize the contents of + * Collections, Maps and arrays.

+ * + *

The output consists of a prefix, the passed in size + * and a suffix.

+ * + *

The default format is '<size=n>'.

+ * + * @param buffer the StringBuffer 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); + } + + /** + *

Is this field to be output in full detail.

+ * + *

This method converts a detail request into a detail level. + * The calling code may request full detail (true), + * but a subclass might ignore that and always return + * false. The calling code may pass in + * null indicating that it doesn't care about + * the detail level. In this case the default detail level is + * used.

+ * + * @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(); + } + + /** + *

Gets the short class name for a class.

+ * + *

The short class name is the classname excluding + * the package name.

+ * + * @param cls the Class 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) + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the class name.

+ * + * @return the current useClassName flag + */ + protected boolean isUseClassName() { + return useClassName; + } + + /** + *

Sets whether to use the class name.

+ * + * @param useClassName the new useClassName flag + */ + protected void setUseClassName(boolean useClassName) { + this.useClassName = useClassName; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output short or long class names.

+ * + * @return the current useShortClassName flag + * @since 2.0 + */ + protected boolean isUseShortClassName() { + return useShortClassName; + } + + /** + *

Sets whether to output short or long class names.

+ * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + protected void setUseShortClassName(boolean useShortClassName) { + this.useShortClassName = useShortClassName; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the identity hash code.

+ * + * @return the current useIdentityHashCode flag + */ + protected boolean isUseIdentityHashCode() { + return useIdentityHashCode; + } + + /** + *

Sets whether to use the identity hash code.

+ * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + protected void setUseIdentityHashCode(boolean useIdentityHashCode) { + this.useIdentityHashCode = useIdentityHashCode; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use the field names passed in.

+ * + * @return the current useFieldNames flag + */ + protected boolean isUseFieldNames() { + return useFieldNames; + } + + /** + *

Sets whether to use the field names passed in.

+ * + * @param useFieldNames the new useFieldNames flag + */ + protected void setUseFieldNames(boolean useFieldNames) { + this.useFieldNames = useFieldNames; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to use full detail when the caller doesn't + * specify.

+ * + * @return the current defaultFullDetail flag + */ + protected boolean isDefaultFullDetail() { + return defaultFullDetail; + } + + /** + *

Sets whether to use full detail when the caller doesn't + * specify.

+ * + * @param defaultFullDetail the new defaultFullDetail flag + */ + protected void setDefaultFullDetail(boolean defaultFullDetail) { + this.defaultFullDetail = defaultFullDetail; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether to output array content detail.

+ * + * @return the current array content detail setting + */ + protected boolean isArrayContentDetail() { + return arrayContentDetail; + } + + /** + *

Sets whether to output array content detail.

+ * + * @param arrayContentDetail the new arrayContentDetail flag + */ + protected void setArrayContentDetail(boolean arrayContentDetail) { + this.arrayContentDetail = arrayContentDetail; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array start text.

+ * + * @return the current array start text + */ + protected String getArrayStart() { + return arrayStart; + } + + /** + *

Sets the array start text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param arrayStart the new array start text + */ + protected void setArrayStart(String arrayStart) { + if (arrayStart == null) { + arrayStart = ""; + } + this.arrayStart = arrayStart; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array end text.

+ * + * @return the current array end text + */ + protected String getArrayEnd() { + return arrayEnd; + } + + /** + *

Sets the array end text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param arrayEnd the new array end text + */ + protected void setArrayEnd(String arrayEnd) { + if (arrayEnd == null) { + arrayEnd = ""; + } + this.arrayEnd = arrayEnd; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the array separator text.

+ * + * @return the current array separator text + */ + protected String getArraySeparator() { + return arraySeparator; + } + + /** + *

Sets the array separator text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param arraySeparator the new array separator text + */ + protected void setArraySeparator(String arraySeparator) { + if (arraySeparator == null) { + arraySeparator = ""; + } + this.arraySeparator = arraySeparator; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content start text.

+ * + * @return the current content start text + */ + protected String getContentStart() { + return contentStart; + } + + /** + *

Sets the content start text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param contentStart the new content start text + */ + protected void setContentStart(String contentStart) { + if (contentStart == null) { + contentStart = ""; + } + this.contentStart = contentStart; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the content end text.

+ * + * @return the current content end text + */ + protected String getContentEnd() { + return contentEnd; + } + + /** + *

Sets the content end text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param contentEnd the new content end text + */ + protected void setContentEnd(String contentEnd) { + if (contentEnd == null) { + contentEnd = ""; + } + this.contentEnd = contentEnd; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field name value separator text.

+ * + * @return the current field name value separator text + */ + protected String getFieldNameValueSeparator() { + return fieldNameValueSeparator; + } + + /** + *

Sets the field name value separator text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param fieldNameValueSeparator the new field name value separator text + */ + protected void setFieldNameValueSeparator(String fieldNameValueSeparator) { + if (fieldNameValueSeparator == null) { + fieldNameValueSeparator = ""; + } + this.fieldNameValueSeparator = fieldNameValueSeparator; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the field separator text.

+ * + * @return the current field separator text + */ + protected String getFieldSeparator() { + return fieldSeparator; + } + + /** + *

Sets the field separator text.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param fieldSeparator the new field separator text + */ + protected void setFieldSeparator(String fieldSeparator) { + if (fieldSeparator == null) { + fieldSeparator = ""; + } + this.fieldSeparator = fieldSeparator; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the start + * of each buffer.

+ * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtStart() { + return fieldSeparatorAtStart; + } + + /** + *

Sets whether the field separator should be added at the start + * of each buffer.

+ * + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected void setFieldSeparatorAtStart(boolean fieldSeparatorAtStart) { + this.fieldSeparatorAtStart = fieldSeparatorAtStart; + } + + //--------------------------------------------------------------------- + + /** + *

Gets whether the field separator should be added at the end + * of each buffer.

+ * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtEnd() { + return fieldSeparatorAtEnd; + } + + /** + *

Sets whether the field separator should be added at the end + * of each buffer.

+ * + * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected void setFieldSeparatorAtEnd(boolean fieldSeparatorAtEnd) { + this.fieldSeparatorAtEnd = fieldSeparatorAtEnd; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the text to output when null found.

+ * + * @return the current text to output when null found + */ + protected String getNullText() { + return nullText; + } + + /** + *

Sets the text to output when null found.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param nullText the new text to output when null found + */ + protected void setNullText(String nullText) { + if (nullText == null) { + nullText = ""; + } + this.nullText = nullText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when a Collection, + * Map or array size is output.

+ * + *

This is output before the size value.

+ * + * @return the current start of size text + */ + protected String getSizeStartText() { + return sizeStartText; + } + + /** + *

Sets the start text to output when a Collection, + * Map or array size is output.

+ * + *

This is output before the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param sizeStartText the new start of size text + */ + protected void setSizeStartText(String sizeStartText) { + if (sizeStartText == null) { + sizeStartText = ""; + } + this.sizeStartText = sizeStartText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when a Collection, + * Map or array size is output.

+ * + *

This is output after the size value.

+ * + * @return the current end of size text + */ + protected String getSizeEndText() { + return sizeEndText; + } + + /** + *

Sets the end text to output when a Collection, + * Map or array size is output.

+ * + *

This is output after the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param sizeEndText the new end of size text + */ + protected void setSizeEndText(String sizeEndText) { + if (sizeEndText == null) { + sizeEndText = ""; + } + this.sizeEndText = sizeEndText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the start text to output when an Object is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + * @return the current start of summary text + */ + protected String getSummaryObjectStartText() { + return summaryObjectStartText; + } + + /** + *

Sets the start text to output when an Object is + * output in summary mode.

+ * + *

This is output before the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param summaryObjectStartText the new start of summary text + */ + protected void setSummaryObjectStartText(String summaryObjectStartText) { + if (summaryObjectStartText == null) { + summaryObjectStartText = ""; + } + this.summaryObjectStartText = summaryObjectStartText; + } + + //--------------------------------------------------------------------- + + /** + *

Gets the end text to output when an Object is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + * @return the current end of summary text + */ + protected String getSummaryObjectEndText() { + return summaryObjectEndText; + } + + /** + *

Sets the end text to output when an Object is + * output in summary mode.

+ * + *

This is output after the size value.

+ * + *

null is accepted, but will be converted to + * an empty String.

+ * + * @param summaryObjectEndText the new end of summary text + */ + protected void setSummaryObjectEndText(String summaryObjectEndText) { + if (summaryObjectEndText == null) { + summaryObjectEndText = ""; + } + this.summaryObjectEndText = summaryObjectEndText; + } + + //---------------------------------------------------------------------------- + + /** + *

Default ToStringStyle.

+ * + *

This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

+ */ + private static final class DefaultToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + DefaultToStringStyle() { + super(); + } + + /** + *

Ensure Singleton after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.DEFAULT_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

ToStringStyle that does not print out + * the field names.

+ * + *

This is an inner class rather than using + * StandardToStringStyle to ensure its immutability. + */ + private static final class NoFieldNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + NoFieldNameToStringStyle() { + super(); + this.setUseFieldNames(false); + } + + /** + *

Ensure Singleton after serialization.

+ * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.NO_FIELD_NAMES_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

ToStringStyle that prints out the short + * class name and no identity hashcode.

+ * + *

This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

+ */ + private static final class ShortPrefixToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + ShortPrefixToStringStyle() { + super(); + this.setUseShortClassName(true); + this.setUseIdentityHashCode(false); + } + + /** + *

Ensure Singleton after serialization.

+ * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.SHORT_PREFIX_STYLE; + } + + } + + /** + *

ToStringStyle that does not print out the + * classname, identity hashcode, content start or field name.

+ * + *

This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

+ */ + private static final class SimpleToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + SimpleToStringStyle() { + super(); + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + this.setUseFieldNames(false); + this.setContentStart(""); + this.setContentEnd(""); + } + + /** + *

Ensure Singleton after serialization.

+ * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.SIMPLE_STYLE; + } + + } + + //---------------------------------------------------------------------------- + + /** + *

ToStringStyle that outputs on multiple lines.

+ * + *

This is an inner class rather than using + * StandardToStringStyle to ensure its immutability.

+ */ + private static final class MultiLineToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + *

Constructor.

+ * + *

Use the static constant rather than instantiating.

+ */ + MultiLineToStringStyle() { + super(); + this.setContentStart("["); + this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " "); + this.setFieldSeparatorAtStart(true); + this.setContentEnd(SystemUtils.LINE_SEPARATOR + "]"); + } + + /** + *

Ensure Singleton after serialization.

+ * + * @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; + +/** + *

+ * An exception that provides an easy and safe way to add contextual information. + *

+ * 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. + *

+ * 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. + *

+ * An unchecked version of this exception is provided by ContextedRuntimeException. + *

+ *

+ * To use this class write code as follows: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (Exception e) {
+ *     throw new ContextedException("Error posting account transaction", e)
+ *          .addContextValue("Account Number", accountNumber)
+ *          .addContextValue("Amount Posted", amountPosted)
+ *          .addContextValue("Previous Balance", previousBalance)
+ *   }
+ * }
+ * 
or improve diagnose data at a higher level: + *
+ *   try {
+ *     ...
+ *   } catch (ContextedException e) {
+ *     throw e.setContextValue("Transaction Id", transactionId);
+ *   } catch (Exception e) {
+ *     if (e instanceof ExceptionContext) {
+ *       e.setContextValue("Transaction Id", transactionId);
+ *     }
+ *     throw e;
+ *   }
+ * }
+ * 
+ *

+ * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

+ * 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)
+ * 
+ *

+ * + * @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. + *

+ * The context information is stored using a default implementation. + */ + public ContextedException() { + super(); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedException with message, but without cause. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Note: This exception is only serializable if the object added is serializable. + *

+ * + * @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. + *

+ * Note: This exception is only serializable if the object added as value is serializable. + *

+ * + * @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 getContextValues(String label) { + return this.exceptionContext.getContextValues(label); + } + + /** + * {@inheritDoc} + */ + public Object getFirstContextValue(String label) { + return this.exceptionContext.getFirstContextValue(label); + } + + /** + * {@inheritDoc} + */ + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + public Set 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; + +/** + *

+ * A runtime exception that provides an easy and safe way to add contextual information. + *

+ * 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. + *

+ * 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. + *

+ * A checked version of this exception is provided by ContextedException. + *

+ *

+ * To use this class write code as follows: + *

+ *
+ *   try {
+ *     ...
+ *   } catch (Exception e) {
+ *     throw new ContextedRuntimeException("Error posting account transaction", e)
+ *          .addContextValue("Account Number", accountNumber)
+ *          .addContextValue("Amount Posted", amountPosted)
+ *          .addContextValue("Previous Balance", previousBalance)
+ *   }
+ * }
+ * 
or improve diagnose data at a higher level: + *
+ *   try {
+ *     ...
+ *   } catch (ContextedRuntimeException e) {
+ *     throw e.setContextValue("Transaction Id", transactionId);
+ *   } catch (Exception e) {
+ *     if (e instanceof ExceptionContext) {
+ *       e.setContextValue("Transaction Id", transactionId);
+ *     }
+ *     throw e;
+ *   }
+ * }
+ * 
+ *

+ * The output in a printStacktrace() (which often is written to a log) would look something like the following: + *

+ * 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)
+ * 
+ *

+ * + * @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. + *

+ * The context information is stored using a default implementation. + */ + public ContextedRuntimeException() { + super(); + exceptionContext = new DefaultExceptionContext(); + } + + /** + * Instantiates ContextedRuntimeException with message, but without cause. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Note: This exception is only serializable if the object added is serializable. + *

+ * + * @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. + *

+ * Note: This exception is only serializable if the object added as value is serializable. + *

+ * + * @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 getContextValues(String label) { + return this.exceptionContext.getContextValues(label); + } + + /** + * {@inheritDoc} + */ + public Object getFirstContextValue(String label) { + return this.exceptionContext.getFirstContextValue(label); + } + + /** + * {@inheritDoc} + */ + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + public Set 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. + *

+ * This implementation is serializable, however this is dependent on the values that + * are added also being serializable. + *

+ * + * @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> contextValues = new ArrayList>(); + + /** + * {@inheritDoc} + */ + public DefaultExceptionContext addContextValue(String label, Object value) { + contextValues.add(new ImmutablePair(label, value)); + return this; + } + + /** + * {@inheritDoc} + */ + public DefaultExceptionContext setContextValue(String label, Object value) { + for (final Iterator> iter = contextValues.iterator(); iter.hasNext();) { + final Pair p = iter.next(); + if (StringUtils.equals(label, p.getKey())) { + iter.remove(); + } + } + addContextValue(label, value); + return this; + } + + /** + * {@inheritDoc} + */ + public List getContextValues(String label) { + final List values = new ArrayList(); + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + values.add(pair.getValue()); + } + } + return values; + } + + /** + * {@inheritDoc} + */ + public Object getFirstContextValue(String label) { + for (final Pair pair : contextValues) { + if (StringUtils.equals(label, pair.getKey())) { + return pair.getValue(); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + public Set getContextLabels() { + final Set labels = new HashSet(); + for (final Pair pair : contextValues) { + labels.add(pair.getKey()); + } + return labels; + } + + /** + * {@inheritDoc} + */ + public List> getContextEntries() { + return contextValues; + } + + /** + * Builds the message containing the contextual information. + * + * @param baseMessage the base exception message without context information appended + * @return the exception message with 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 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. + *

+ * Implementations are expected to manage the pairs in a list-style collection + * that keeps the pairs in the sequence of their addition. + *

+ * + * @see ContextedException + * @see ContextedRuntimeException + * @since 3.0 + */ +public interface ExceptionContext { + + /** + * Adds a contextual label-value pair into this context. + *

+ * The pair will be added to the context, independently of an already + * existing pair with the same label. + *

+ * + * @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. + *

+ * The pair will be added normally, but any existing label-value pair with + * the same label is removed from the context. + *

+ * + * @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 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 getContextLabels(); + + /** + * Retrieves the full list of label-value pairs defined in the contextual data. + * + * @return the list of pairs, not {@code null} + */ + public List> 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 without context information appended + * @return the exception message with 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; + +/** + *

Provides utilities for manipulating and examining + * Throwable objects.

+ * + * @since 1.0 + * @version $Id: ExceptionUtils.java 1144929 2011-07-10 18:26:16Z ggregory $ + */ +public class ExceptionUtils { + + /** + *

Used when printing stack frames to denote the start of a + * wrapped exception.

+ * + *

Package private for accessibility by test suite.

+ */ + static final String WRAPPED_MARKER = " [wrapped] "; + + /** + *

The names of methods commonly used to access a wrapped exception.

+ */ + // 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", + }; + + /** + *

+ * Public constructor allows an instance of ExceptionUtils to be created, although that is not + * normally necessary. + *

+ */ + public ExceptionUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

Returns the default names used when searching for the cause of an exception.

+ * + *

This may be modified and used in the overloaded getCause(Throwable, String[]) method.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Introspects the Throwable to obtain the cause.

+ * + *

The method searches for methods with specific names that return a + * Throwable object. This will pick up most wrapping exceptions, + * including those from JDK 1.4. + * + *

The default list searched for are:

+ *
    + *
  • getCause()
  • + *
  • getNextException()
  • + *
  • getTargetException()
  • + *
  • getException()
  • + *
  • getSourceException()
  • + *
  • getRootCause()
  • + *
  • getCausedByException()
  • + *
  • getNested()
  • + *
+ * + *

If none of the above is found, returns null.

+ * + * @param throwable the throwable to introspect for a cause, may be null + * @return the cause of the Throwable, + * null 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); + } + + /** + *

Introspects the Throwable to obtain the cause.

+ * + *

A null set of method names means use the default set. + * A null in the set of method names will be ignored.

+ * + * @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 Throwable, + * null 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; + } + + /** + *

Introspects the Throwable to obtain the root cause.

+ * + *

This method walks through the exception chain to the last element, + * "root" of the tree, using {@link #getCause(Throwable)}, and + * returns that exception.

+ * + *

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.

+ * + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the Throwable, + * null if none found or null throwable input + */ + public static Throwable getRootCause(Throwable throwable) { + List list = getThrowableList(throwable); + return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1)); + } + + /** + *

Finds a Throwable by method name.

+ * + * @param throwable the exception to examine + * @param methodName the name of the method to find and invoke + * @return the wrapped exception, or null 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; + } + + //----------------------------------------------------------------------- + /** + *

Counts the number of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return 1. + * A throwable with one cause will return 2 and so on. + * A null throwable will return 0.

+ * + *

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.

+ * + * @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(); + } + + /** + *

Returns the list of Throwable objects in the + * exception chain.

+ * + *

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 null throwable will return an array of size zero.

+ * + *

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.

+ * + * @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 list = getThrowableList(throwable); + return list.toArray(new Throwable[list.size()]); + } + + /** + *

Returns the list of Throwable objects in the + * exception chain.

+ * + *

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 null throwable will return a list of size zero.

+ * + *

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.

+ * + * @param throwable the throwable to inspect, may be null + * @return the list of throwables, never null + * @since Commons Lang 2.2 + */ + public static List getThrowableList(Throwable throwable) { + List list = new ArrayList(); + while (throwable != null && list.contains(throwable) == false) { + list.add(throwable); + throwable = ExceptionUtils.getCause(throwable); + } + return list; + } + + //----------------------------------------------------------------------- + /** + *

Returns the (zero based) index of the first Throwable + * 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.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

+ * + * @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); + } + + /** + *

Returns the (zero based) index of the first Throwable + * 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.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Returns the (zero based) index of the first Throwable + * 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.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

+ * + * @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); + } + + /** + *

Returns the (zero based) index of the first Throwable + * 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.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

+ * + * @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); + } + + /** + *

Worker method for the indexOfType methods.

+ * + * @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 true, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares + * using references + * @return index of the type within throwables nested withing the specified throwable + */ + 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; + } + + //----------------------------------------------------------------------- + /** + *

Prints a compact stack trace for the root cause of a throwable + * to System.err.

+ * + *

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.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

+ * + * @param throwable the throwable to output + * @since 2.0 + */ + public static void printRootCauseStackTrace(Throwable throwable) { + printRootCauseStackTrace(throwable, System.err); + } + + /** + *

Prints a compact stack trace for the root cause of a throwable.

+ * + *

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.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

+ * + * @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 null + * @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(); + } + + /** + *

Prints a compact stack trace for the root cause of a throwable.

+ * + *

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.

+ * + *

The output of this method is consistent across JDK versions. + * Note that this is the opposite order to the JDK1.4 display.

+ * + *

The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

+ * + * @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 null + * @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(); + } + + //----------------------------------------------------------------------- + /** + *

Creates a compact stack trace for the root cause of the supplied + * Throwable.

+ * + *

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.

+ * + * @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 frames = new ArrayList(); + List nextTrace = getStackFrameList(throwables[count - 1]); + for (int i = count; --i >= 0;) { + List 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()]); + } + + /** + *

Removes common frames from the cause trace given the two stack traces.

+ * + * @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 causeFrames, List 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--; + } + } + + //----------------------------------------------------------------------- + /** + *

Gets the stack trace from a Throwable as a String.

+ * + *

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.

+ * + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + * printStackTrace(PrintWriter) method + */ + public static String getStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + /** + *

Captures the stack trace associated with the specified + * Throwable object, decomposing it into a list of + * stack frames.

+ * + *

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.

+ * + * @param throwable the Throwable 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)); + } + + //----------------------------------------------------------------------- + /** + *

Returns an array where each element is a line from the argument.

+ * + *

The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.

+ * + * @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 list = new ArrayList(); + while (frames.hasMoreTokens()) { + list.add(frames.nextToken()); + } + return list.toArray(new String[list.size()]); + } + + /** + *

Produces a List of stack frames - the message + * is not included. Only the trace of the specified exception is + * returned, any caused by trace is stripped.

+ * + *

This works in most cases - it will only fail if the exception + * message contains a line that starts with: + * "   at".

+ * + * @param t is any throwable + * @return List of stack frames + */ + static List getStackFrameList(Throwable t) { + String stackTrace = getStackTrace(t); + String linebreak = SystemUtils.LINE_SEPARATOR; + StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); + List list = new ArrayList(); + boolean traceStarted = false; + while (frames.hasMoreTokens()) { + String token = frames.nextToken(); + // Determine if the line starts with 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. + *

+ * 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. + *

+ * 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. + *

+ * Mutable is used as a generic interface to the implementations in this package. + *

+ * 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 the type to set and get + * @version $Id: Mutable.java 1153213 2011-08-02 17:35:39Z ggregory $ + */ +public interface Mutable { + + /** + * 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 boolean wrapper. + *

+ * 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, Serializable, Comparable { + + /** + * 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 true. + * + * @return true if the current value is true + * @since 2.5 + */ + public boolean isTrue() { + return value == true; + } + + /** + * Checks if the current value is false. + * + * @return true if the current value is false + * @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 true if and only if the argument is + * not null and is an MutableBoolean object that contains the same + * boolean value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 Boolean.TRUE or Boolean.FALSE + */ + @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 byte wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument is + * not null and is a MutableByte object that contains the same byte value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 double wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument + * is not null and is a Double 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 + * double 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. + *

+ * Note that in most cases, for two instances of class Double,d1 and d2, + * the value of d1.equals(d2) is true if and only if

+ * + *
+     *   d1.doubleValue() == d2.doubleValue()
+     * 
+ * + *
+ *

+ * also has the value true. However, there are two exceptions: + *

    + *
  • If d1 and d2 both represent Double.NaN, then the + * equals method returns true, even though Double.NaN==Double.NaN has + * the value false. + *
  • If d1 represents +0.0 while d2 represents -0.0, + * or vice versa, the equal test has the value false, even though + * +0.0==-0.0 has the value true. This allows hashtables to operate properly. + *
+ * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 float wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument is + * not null and is a Float object that represents a float that has the + * identical bit pattern to the bit pattern of the float 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. + *

+ * Note that in most cases, for two instances of class Float,f1 and f2, + * the value of f1.equals(f2) is true if and only if

+ * + *
+     *   f1.floatValue() == f2.floatValue()
+     * 
+ * + *
+ *

+ * also has the value true. However, there are two exceptions: + *

    + *
  • If f1 and f2 both represent Float.NaN, then the + * equals method returns true, even though Float.NaN==Float.NaN has + * the value false. + *
  • If f1 represents +0.0f while f2 represents -0.0f, + * or vice versa, the equal test has the value false, even though + * 0.0f==-0.0f has the value true. + *
+ * This definition allows hashtables to operate properly. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 int wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument is + * not null and is a MutableInt object that contains the same int value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 long wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument + * is not null and is a MutableLong object that contains the same long + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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 Object wrapper. + * + * @since 2.1 + * @version $Id: MutableObject.java 1088899 2011-04-05 05:31:27Z bayard $ + */ +public class MutableObject implements Mutable, 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 null. + */ + 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; + } + + //----------------------------------------------------------------------- + /** + *

+ * Compares this object against the specified object. The result is true if and only if the argument + * is not null and is a MutableObject object that contains the same T + * value as this object. + *

+ * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; + * true if the objects have equivalent value fields; + * false 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 0 if the value is null. + * + * @return the value's hash code or 0 if the value is null. + */ + @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 short wrapper. + *

+ * 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, Mutable { + + /** + * 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 true if and only if the argument + * is not null and is a MutableShort object that contains the same short + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return true if the objects are the same; false 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; + +/** + *

Date and time formatting utilities and constants.

+ * + *

Formatting is performed using the thread-safe + * {@link org.apache.commons.lang3.time.FastDateFormat} class.

+ * + * @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 yyyy-MM-dd'T'HH:mm:ss. + */ + 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 yyyy-MM-dd'T'HH:mm:ssZZ. + */ + 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 yyyy-MM-dd. + */ + public static final FastDateFormat ISO_DATE_FORMAT + = FastDateFormat.getInstance("yyyy-MM-dd"); + + /** + * ISO8601-like formatter for date with time zone. + * The format used is yyyy-MM-ddZZ. + * 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 'T'HH:mm:ss. + */ + public static final FastDateFormat ISO_TIME_FORMAT + = FastDateFormat.getInstance("'T'HH:mm:ss"); + + /** + * ISO8601 formatter for time with time zone. + * The format used is 'T'HH:mm:ssZZ. + */ + 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 HH:mm:ss. + * 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 HH:mm:ssZZ. + * 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 EEE, dd MMM yyyy HH:mm:ss Z in US locale. + */ + public static final FastDateFormat SMTP_DATETIME_FORMAT + = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); + + //----------------------------------------------------------------------- + /** + *

DateFormatUtils instances should NOT be constructed in standard programming.

+ * + *

This constructor is public to permit tools that require a JavaBean instance + * to operate.

+ */ + public DateFormatUtils() { + super(); + } + + /** + *

Formats a date/time into a specific pattern using the UTC time zone.

+ * + * @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); + } + + /** + *

Formats a date/time into a specific pattern using the UTC time zone.

+ * + * @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); + } + + /** + *

Formats a date/time into a specific pattern using the UTC time zone.

+ * + * @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 null + * @return the formatted date + */ + public static String formatUTC(long millis, String pattern, Locale locale) { + return format(new Date(millis), pattern, UTC_TIME_ZONE, locale); + } + + /** + *

Formats a date/time into a specific pattern using the UTC time zone.

+ * + * @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 null + * @return the formatted date + */ + public static String formatUTC(Date date, String pattern, Locale locale) { + return format(date, pattern, UTC_TIME_ZONE, locale); + } + + /** + *

Formats a date/time into a specific pattern.

+ * + * @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); + } + + /** + *

Formats a date/time into a specific pattern.

+ * + * @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); + } + + /** + *

Formats a calendar into a specific pattern.

+ * + * @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); + } + + /** + *

Formats a date/time into a specific pattern in a time zone.

+ * + * @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 null + * @return the formatted date + */ + public static String format(long millis, String pattern, TimeZone timeZone) { + return format(new Date(millis), pattern, timeZone, null); + } + + /** + *

Formats a date/time into a specific pattern in a time zone.

+ * + * @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 null + * @return the formatted date + */ + public static String format(Date date, String pattern, TimeZone timeZone) { + return format(date, pattern, timeZone, null); + } + + /** + *

Formats a calendar into a specific pattern in a time zone.

+ * + * @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 null + * @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); + } + + /** + *

Formats a date/time into a specific pattern in a locale.

+ * + * @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 null + * @return the formatted date + */ + public static String format(long millis, String pattern, Locale locale) { + return format(new Date(millis), pattern, null, locale); + } + + /** + *

Formats a date/time into a specific pattern in a locale.

+ * + * @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 null + * @return the formatted date + */ + public static String format(Date date, String pattern, Locale locale) { + return format(date, pattern, null, locale); + } + + /** + *

Formats a calendar into a specific pattern in a locale.

+ * + * @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 null + * @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); + } + + /** + *

Formats a date/time into a specific pattern in a time zone and locale.

+ * + * @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 null + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(long millis, String pattern, TimeZone timeZone, Locale locale) { + return format(new Date(millis), pattern, timeZone, locale); + } + + /** + *

Formats a date/time into a specific pattern in a time zone and locale.

+ * + * @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 null + * @param locale the locale to use, may be null + * @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); + } + + /** + *

Formats a calendar into a specific pattern in a time zone and locale.

+ * + * @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 null + * @param locale the locale to use, may be null + * @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; + +/** + *

A suite of utilities surrounding the use of the + * {@link java.util.Calendar} and {@link java.util.Date} object.

+ * + *

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. + *

+ * + * @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; + + /** + *

{@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);}.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public DateUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + *

Checks if two date objects are on the same day ignoring time.

+ * + *

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. + *

+ * + * @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 null + * @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); + } + + /** + *

Checks if two calendar objects are on the same day ignoring time.

+ * + *

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. + *

+ * + * @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 null + * @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)); + } + + //----------------------------------------------------------------------- + /** + *

Checks if two date objects represent the same instant in time.

+ * + *

This method compares the long millisecond time of the two objects.

+ * + * @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 null + * @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(); + } + + /** + *

Checks if two calendar objects represent the same instant in time.

+ * + *

This method compares the long millisecond time of the two objects.

+ * + * @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 null + * @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(); + } + + //----------------------------------------------------------------------- + /** + *

Checks if two calendar objects represent the same local time.

+ * + *

This method compares the values of the fields of the two objects. + * In addition, both calendars must be the same of the same type.

+ * + * @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 null + * @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()); + } + + //----------------------------------------------------------------------- + /** + *

Parses a string representing a date by trying a variety of different parsers.

+ * + *

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.

+ * 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); + } + + //----------------------------------------------------------------------- + /** + *

Parses a string representing a date by trying a variety of different parsers.

+ * + *

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.

+ * 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); + } + + /** + *

Parses a string representing a date by trying a variety of different parsers.

+ * + *

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.

+ * + * @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; + } + + //----------------------------------------------------------------------- + /** + *

Round this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + *

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: + *

    + *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • + *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • + *
+ *

+ * + * @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(); + } + + /** + *

Round this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + *

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: + *

    + *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • + *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • + *
+ *

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different rounded date, not null + * @throws IllegalArgumentException if the date is null + * @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; + } + + /** + *

Round this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + *

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: + *

    + *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • + *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • + *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • + *
+ *

+ * + * @param date the date to work with, either {@code Date} or {@code Calendar}, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different rounded date, not null + * @throws IllegalArgumentException if the date is null + * @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); + } + } + + //----------------------------------------------------------------------- + /** + *

Truncate this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different truncated date, not null + * @throws IllegalArgumentException if the date is null + * @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(); + } + + /** + *

Truncate this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different truncated date, not null + * @throws IllegalArgumentException if the date is null + * @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; + } + + /** + *

Truncate this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, either {@code Date} or {@code Calendar}, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different truncated date, not null + * @throws IllegalArgumentException if the date is null + * @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); + } + } + + //----------------------------------------------------------------------- + /** + *

Ceil this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different ceil date, not null + * @throws IllegalArgumentException if the date is null + * @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(); + } + + /** + *

Ceil this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different ceil date, not null + * @throws IllegalArgumentException if the date is null + * @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; + } + + /** + *

Ceil this date, leaving the field specified as the most + * significant field.

+ * + *

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.

+ * + * @param date the date to work with, either {@code Date} or {@code Calendar}, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different ceil date, not null + * @throws IllegalArgumentException if the date is null + * @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()); + } + } + + //----------------------------------------------------------------------- + /** + *

Internal calculation method.

+ * + * @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"); + + } + + //----------------------------------------------------------------------- + /** + *

This constructs an Iterator over each day in a date + * range defined by a focus date and range style.

+ * + *

For instance, passing Thursday, July 4, 2002 and a + * RANGE_MONTH_SUNDAY will return an Iterator + * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, + * 2002, returning a Calendar instance for each intermediate day.

+ * + *

This method provides an iterator that returns Calendar objects. + * The days are progressed using {@link Calendar#add(int, int)}.

+ * + * @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 null + * @throws IllegalArgumentException if the rangeStyle is invalid + */ + public static Iterator 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); + } + + /** + *

This constructs an Iterator over each day in a date + * range defined by a focus date and range style.

+ * + *

For instance, passing Thursday, July 4, 2002 and a + * RANGE_MONTH_SUNDAY will return an Iterator + * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, + * 2002, returning a Calendar instance for each intermediate day.

+ * + *

This method provides an iterator that returns Calendar objects. + * The days are progressed using {@link Calendar#add(int, int)}.

+ * + * @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 null + * @throws IllegalArgumentException if the rangeStyle is invalid + */ + public static Iterator 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); + } + + /** + *

This constructs an Iterator over each day in a date + * range defined by a focus date and range style.

+ * + *

For instance, passing Thursday, July 4, 2002 and a + * RANGE_MONTH_SUNDAY will return an Iterator + * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, + * 2002, returning a Calendar instance for each intermediate day.

+ * + * @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 null + * @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); + } + } + + /** + *

Returns the number of milliseconds within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 (10*1000 + 538)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in milliseconds)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMilliseconds(Date date, int fragment) { + return getFragment(date, fragment, Calendar.MILLISECOND); + } + + /** + *

Returns the number of seconds within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 + * (equivalent to deprecated date.getSeconds())
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 + * (equivalent to deprecated date.getSeconds())
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110 + * (7*3600 + 15*60 + 10)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in seconds)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInSeconds(Date date, int fragment) { + return getFragment(date, fragment, Calendar.SECOND); + } + + /** + *

Returns the number of minutes within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 + * (equivalent to deprecated date.getMinutes())
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 + * (equivalent to deprecated date.getMinutes())
  • + *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in minutes)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMinutes(Date date, int fragment) { + return getFragment(date, fragment, Calendar.MINUTE); + } + + /** + *

Returns the number of hours within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7 + * (equivalent to deprecated date.getHours())
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7 + * (equivalent to deprecated date.getHours())
  • + *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in hours)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInHours(Date date, int fragment) { + return getFragment(date, fragment, Calendar.HOUR_OF_DAY); + } + + /** + *

Returns the number of days within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 28, 2008 with Calendar.MONTH as fragment will return 28 + * (equivalent to deprecated date.getDay())
  • + *
  • February 28, 2008 with Calendar.MONTH as fragment will return 28 + * (equivalent to deprecated date.getDay())
  • + *
  • January 28, 2008 with Calendar.YEAR as fragment will return 28
  • + *
  • February 28, 2008 with Calendar.YEAR as fragment will return 59
  • + *
  • January 28, 2008 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in days)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInDays(Date date, int fragment) { + return getFragment(date, fragment, Calendar.DAY_OF_YEAR); + } + + /** + *

Returns the number of milliseconds within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538 + * (equivalent to calendar.get(Calendar.MILLISECOND))
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538 + * (equivalent to calendar.get(Calendar.MILLISECOND))
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 + * (10*1000 + 538)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in milliseconds)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMilliseconds(Calendar calendar, int fragment) { + return getFragment(calendar, fragment, Calendar.MILLISECOND); + } + /** + *

Returns the number of seconds within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 + * (equivalent to calendar.get(Calendar.SECOND))
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 + * (equivalent to calendar.get(Calendar.SECOND))
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110 + * (7*3600 + 15*60 + 10)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in seconds)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInSeconds(Calendar calendar, int fragment) { + return getFragment(calendar, fragment, Calendar.SECOND); + } + + /** + *

Returns the number of minutes within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 + * (equivalent to calendar.get(Calendar.MINUTES))
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 + * (equivalent to calendar.get(Calendar.MINUTES))
  • + *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in minutes)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMinutes(Calendar calendar, int fragment) { + return getFragment(calendar, fragment, Calendar.MINUTE); + } + + /** + *

Returns the number of hours within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • 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))
  • + *
  • 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))
  • + *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7
  • + *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)
  • + *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in hours)
  • + *
+ *

+ * + * @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 null or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInHours(Calendar calendar, int fragment) { + return getFragment(calendar, fragment, Calendar.HOUR_OF_DAY); + } + + /** + *

Returns the number of days within the + * fragment. All datefields greater than the fragment will be ignored.

+ * + *

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).

+ * + *

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.

+ * + *

+ *

    + *
  • January 28, 2008 with Calendar.MONTH as fragment will return 28 + * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))
  • + *
  • February 28, 2008 with Calendar.MONTH as fragment will return 28 + * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))
  • + *
  • January 28, 2008 with Calendar.YEAR as fragment will return 28 + * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))
  • + *
  • February 28, 2008 with Calendar.YEAR as fragment will return 59 + * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))
  • + *
  • January 28, 2008 with Calendar.MILLISECOND as fragment will return 0 + * (a millisecond cannot be split in days)
  • + *
+ *

+ * + * @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 null 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 null 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 null 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 null + * @param cal2 the second calendar, not null + * @param field the field from {@code Calendar} + * @return true if equal; otherwise false + * @throws IllegalArgumentException if any argument is null + * @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 null + * @param date2 the second date, not null + * @param field the field from {@code Calendar} + * @return true if equal; otherwise false + * @throws IllegalArgumentException if any argument is null + * @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 null + * @param cal2 the second calendar, not null + * @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 null + * @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 null + * @param date2 the second date, not null + * @param field the field from Calendar + * @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 null + * @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; + } + + //----------------------------------------------------------------------- + /** + *

Date iterator.

+ */ + static class DateIterator implements Iterator { + 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 true 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; + +/** + *

Duration formatting utilities and constants. The following table describes the tokens + * used in the pattern language for formatting.

+ * + * + * + * + * + * + * + * + * + *
characterduration element
yyears
Mmonths
ddays
Hhours
mminutes
sseconds
Smilliseconds
+ * + * @since 2.1 + * @version $Id: DurationFormatUtils.java 1144993 2011-07-11 00:51:16Z ggregory $ + */ +public class DurationFormatUtils { + + /** + *

DurationFormatUtils instances should NOT be constructed in standard programming.

+ * + *

This constructor is public to permit tools that require a JavaBean instance + * to operate.

+ */ + public DurationFormatUtils() { + super(); + } + + /** + *

Pattern used with FastDateFormat and SimpleDateFormat + * for the ISO8601 period format used in durations.

+ * + * @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'"; + + //----------------------------------------------------------------------- + /** + *

Formats the time gap as a string.

+ * + *

The format used is ISO8601-like: + * H:m:s.S.

+ * + * @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"); + } + + /** + *

Formats the time gap as a string.

+ * + *

The format used is the ISO8601 period format.

+ * + *

This method formats durations using the days and lower fields of the + * ISO format pattern, such as P7D6TH5M4.321S.

+ * + * @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); + } + + /** + *

Formats the time gap as a string, using the specified format, and padding with zeros and + * using the default timezone.

+ * + *

This method formats durations using the days and lower fields of the + * format pattern. Months and larger are not used.

+ * + * @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); + } + + /** + *

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.

+ * + *

This method formats durations using the days and lower fields of the + * format pattern. Months and larger are not used.

+ * + * @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); + } + + /** + *

Formats an elapsed time into a plurialization correct string.

+ * + *

This method formats durations using the days and lower fields of the + * format pattern. Months and larger are not used.

+ * + * @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(); + } + + //----------------------------------------------------------------------- + /** + *

Formats the time gap as a string.

+ * + *

The format used is the ISO8601 period format.

+ * + * @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()); + } + + /** + *

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()); + } + + /** + *

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.

+ * + *

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.

+ * + *

For more control, the Joda-Time + * library is recommended.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

The internal method to do the formatting.

+ * + * @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 list = new ArrayList(array.length); + + boolean inLiteral = false; + StringBuffer buffer = null; + Token previous = null; + int sz = array.length; + for(int i=0; itrue 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 true 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; + +/** + *

FastDateFormat is a fast and thread-safe version of + * {@link java.text.SimpleDateFormat}.

+ * + *

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. + *

+ * + *

Only formatting is supported, but all patterns are compatible with + * SimpleDateFormat (except time zones and some year patterns - see below).

+ * + *

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).

+ * + *

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.

+ * + *

Javadoc cites for the year pattern: For formatting, if the number of + * pattern letters is 2, the year is truncated to 2 digits; otherwise it is + * interpreted as a number. 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.

+ * + * @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 cache= new FormatCache() { + @Override + protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) { + return new FastDateFormat(pattern, timeZone, locale); + } + }; + + private static ConcurrentMap cTimeZoneDisplayCache = + new ConcurrentHashMap(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; + + //----------------------------------------------------------------------- + /** + *

Gets a formatter instance using the default pattern in the + * default locale.

+ * + * @return a date/time formatter + */ + public static FastDateFormat getInstance() { + return cache.getDateTimeInstance(SHORT, SHORT, null, null); + } + + /** + *

Gets a formatter instance using the specified pattern in the + * default locale.

+ * + * @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); + } + + /** + *

Gets a formatter instance using the specified pattern and + * time zone.

+ * + * @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); + } + + /** + *

Gets a formatter instance using the specified pattern and + * locale.

+ * + * @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); + } + + /** + *

Gets a formatter instance using the specified pattern, time zone + * and locale.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Gets a date formatter instance using the specified style in the + * default time zone and locale.

+ * + * @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); + } + + /** + *

Gets a date formatter instance using the specified style and + * locale in the default time zone.

+ * + * @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); + } + + /** + *

Gets a date formatter instance using the specified style and + * time zone in the default locale.

+ * + * @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); + } + + /** + *

Gets a date formatter instance using the specified style, time + * zone and locale.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Gets a time formatter instance using the specified style in the + * default time zone and locale.

+ * + * @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); + } + + /** + *

Gets a time formatter instance using the specified style and + * locale in the default time zone.

+ * + * @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); + } + + /** + *

Gets a time formatter instance using the specified style and + * time zone in the default locale.

+ * + * @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); + } + + /** + *

Gets a time formatter instance using the specified style, time + * zone and locale.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Gets a date/time formatter instance using the specified style + * in the default time zone and locale.

+ * + * @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); + } + + /** + *

Gets a date/time formatter instance using the specified style and + * locale in the default time zone.

+ * + * @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); + } + + /** + *

Gets a date/time formatter instance using the specified style and + * time zone in the default locale.

+ * + * @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); + } + /** + *

Gets a date/time formatter instance using the specified style, + * time zone and locale.

+ * + * @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); + } + + //----------------------------------------------------------------------- + /** + *

Gets the time zone display name, using a cache for performance.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Constructs a new FastDateFormat.

+ * + * @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(); + } + + /** + *

Initializes the instance for first use.

+ */ + private void init() { + List 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 + //----------------------------------------------------------------------- + /** + *

Returns a list of Rules given a pattern.

+ * + * @return a {@code List} of Rule objects + * @throws IllegalArgumentException if pattern is invalid + */ + protected List parsePattern() { + DateFormatSymbols symbols = new DateFormatSymbols(mLocale); + List rules = new ArrayList(); + + 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; + } + + /** + *

Performs the parsing of tokens.

+ * + * @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(); + } + + /** + *

Gets an appropriate rule for the padding required.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Formats a {@code Date}, {@code Calendar} or + * {@code Long} (milliseconds) object.

+ * + * @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 ? "" : obj.getClass().getName())); + } + } + + /** + *

Formats a millisecond {@code long} value.

+ * + * @param millis the millisecond value to format + * @return the formatted string + * @since 2.1 + */ + public String format(long millis) { + return format(new Date(millis)); + } + + /** + *

Formats a {@code Date} object using a {@code GregorianCalendar}.

+ * + * @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(); + } + + /** + *

Formats a {@code Calendar} object.

+ * + * @param calendar the calendar to format + * @return the formatted string + */ + public String format(Calendar calendar) { + return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString(); + } + + /** + *

Formats a milliseond {@code long} value into the + * supplied {@code StringBuffer}.

+ * + * @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); + } + + /** + *

Formats a {@code Date} object into the + * supplied {@code StringBuffer} using a {@code GregorianCalendar}.

+ * + * @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); + } + + /** + *

Formats a {@code Calendar} object into the + * supplied {@code StringBuffer}.

+ * + * @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); + } + + /** + *

Performs the formatting by applying the rules to the + * specified calendar.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Parsing is not supported.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Gets the pattern used by this formatter.

+ * + * @return the pattern, {@link java.text.SimpleDateFormat} compatible + */ + public String getPattern() { + return mPattern; + } + + /** + *

Gets the time zone used by this formatter.

+ * + *

This zone is always used for {@code Date} formatting.

+ * + * @return the time zone + */ + public TimeZone getTimeZone() { + return mTimeZone; + } + + /** + *

Gets the locale used by this formatter.

+ * + * @return the locale + */ + public Locale getLocale() { + return mLocale; + } + + /** + *

Gets an estimate for the maximum string length that the + * formatter will produce.

+ * + *

The actual formatted length will almost always be less than or + * equal to this amount.

+ * + * @return the maximum formatted length + */ + public int getMaxLengthEstimate() { + return mMaxLengthEstimate; + } + + // Basics + //----------------------------------------------------------------------- + /** + *

Compares two objects for equality.

+ * + * @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); + } + + /** + *

Returns a hashcode compatible with equals.

+ * + * @return a hashcode compatible with equals + */ + @Override + public int hashCode() { + return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); + } + + /** + *

Gets a debugging string version of this formatter.

+ * + * @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 + //----------------------------------------------------------------------- + /** + *

Inner class defining a rule.

+ */ + 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); + } + + /** + *

Inner class defining a numeric rule.

+ */ + 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); + } + + /** + *

Inner class to output a constant single character.

+ */ + 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); + } + } + + /** + *

Inner class to output a constant string.

+ */ + 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); + } + } + + /** + *

Inner class to output one of a set of values.

+ */ + 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)]); + } + } + + /** + *

Inner class to output an unpadded number.

+ */ + 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)); + } + } + } + + /** + *

Inner class to output an unpadded month.

+ */ + 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')); + } + } + } + + /** + *

Inner class to output a padded number.

+ */ + 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)); + } + } + } + + /** + *

Inner class to output a two digit number.

+ */ + 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)); + } + } + } + + /** + *

Inner class to output a two digit year.

+ */ + 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')); + } + } + + /** + *

Inner class to output a two digit month.

+ */ + 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')); + } + } + + /** + *

Inner class to output the twelve hour field.

+ */ + 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); + } + } + + /** + *

Inner class to output the twenty four hour field.

+ */ + 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); + } + } + + /** + *

Inner class to output a time zone name.

+ */ + 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); + } + } + } + + /** + *

Inner class to output a time zone as a number {@code +/-HHMM} + * or {@code +/-HH:MM}.

+ */ + 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')); + } + } + + // ---------------------------------------------------------------------- + /** + *

Inner class that acts as a compound key for time zone names.

+ */ + 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; + +/** + *

FormatCache is a cache and factory for {@link Format}s.

+ * + * @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 { + /** + * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG + */ + static final int NONE= -1; + + private final ConcurrentMap cInstanceCache + = new ConcurrentHashMap(7); + + private final ConcurrentMap cDateTimeInstanceCache + = new ConcurrentHashMap(7); + + /** + *

Gets a formatter instance using the default pattern in the + * default timezone and locale.

+ * + * @return a date/time formatter + */ + public F getInstance() { + return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); + } + + /** + *

Gets a formatter instance using the specified pattern, time zone + * and locale.

+ * + * @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 null + */ + 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; + } + + /** + *

Create a format instance using the specified pattern, time zone + * and locale.

+ * + * @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 null + */ + abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); + + /** + *

Gets a date/time formatter instance using the specified style, + * time zone and locale.

+ * + * @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); + } + + // ---------------------------------------------------------------------- + /** + *

Helper class to hold multi-part Map keys

+ */ + private static class MultipartKey { + private final Object[] keys; + private int hashCode; + + /** + * Constructs an instance of MultipartKey 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; + +/** + *

+ * StopWatch provides a convenient API for timings. + *

+ * + *

+ * To start the watch, call {@link #start()}. At this point you can: + *

+ *
    + *
  • {@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.
  • + *
  • {@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.
  • + *
  • {@link #stop()} the watch to complete the timing session.
  • + *
+ * + *

+ * 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. + *

+ * + *

+ * 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. + *

+ * + *

+ * 1. split(), suspend(), or stop() cannot be invoked twice
+ * 2. unsplit() may only be called if the watch has been split()
+ * 3. resume() may only be called if the watch has been suspend()
+ * 4. start() cannot be called twice without calling reset() + *

+ * + *

This class is not thread-safe

+ * + * @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; + + /** + *

+ * Constructor. + *

+ */ + public StopWatch() { + super(); + } + + /** + *

+ * Start the stopwatch. + *

+ * + *

+ * This method starts a new timing session, clearing any previous values. + *

+ * + * @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; + } + + /** + *

+ * Stop the stopwatch. + *

+ * + *

+ * This method ends a new timing session, allowing the time to be retrieved. + *

+ * + * @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; + } + + /** + *

+ * Resets the stopwatch. Stops it if need be. + *

+ * + *

+ * This method clears the internal values to allow the object to be reused. + *

+ */ + public void reset() { + this.runningState = STATE_UNSTARTED; + this.splitState = STATE_UNSPLIT; + } + + /** + *

+ * Split the time. + *

+ * + *

+ * 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. + *

+ * + * @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; + } + + /** + *

+ * Remove a split. + *

+ * + *

+ * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to + * continue. + *

+ * + * @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; + } + + /** + *

+ * Suspend the stopwatch for later resumption. + *

+ * + *

+ * 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. + *

+ * + * @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; + } + + /** + *

+ * Resume the stopwatch after a suspend. + *

+ * + *

+ * 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. + *

+ * + * @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; + } + + /** + *

+ * Get the time on the stopwatch. + *

+ * + *

+ * This is either the time between the start and the moment this method is called, or the amount of time between + * start and stop. + *

+ * + * @return the time in milliseconds + */ + public long getTime() { + return getNanoTime() / NANO_2_MILLIS; + } + /** + *

+ * Get the time on the stopwatch in nanoseconds. + *

+ * + *

+ * This is either the time between the start and the moment this method is called, or the amount of time between + * start and stop. + *

+ * + * @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. "); + } + + /** + *

+ * Get the split time on the stopwatch. + *

+ * + *

+ * This is the time between start and latest split. + *

+ * + * @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; + } + /** + *

+ * Get the split time on the stopwatch in nanoseconds. + *

+ * + *

+ * This is the time between start and latest split. + *

+ * + * @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; + } + + /** + *

+ * Gets a summary of the time that the stopwatch recorded as a string. + *

+ * + *

+ * The format used is ISO8601-like, hours:minutes:seconds.milliseconds. + *

+ * + * @return the time as a String + */ + @Override + public String toString() { + return DurationFormatUtils.formatDurationHMS(getTime()); + } + + /** + *

+ * Gets a summary of the split time that the stopwatch recorded as a string. + *

+ * + *

+ * The format used is ISO8601-like, hours:minutes:seconds.milliseconds. + *

+ * + * @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; + +/** + *

An immutable pair consisting of two {@code Object} elements.

+ * + *

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.

+ * + *

#ThreadSafe# if the objects are threadsafe

+ * + * @param the left element type + * @param the right element type + * + * @since Lang 3.0 + * @version $Id: ImmutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $ + */ +public final class ImmutablePair extends Pair { + + /** Serialization version */ + private static final long serialVersionUID = 4954918890077093841L; + + /** Left object */ + public final L left; + /** Right object */ + public final R right; + + /** + *

Obtains an immutable pair of from two objects inferring the generic types.

+ * + *

This factory allows the pair to be created using inference to + * obtain the generic types.

+ * + * @param the left element type + * @param 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 ImmutablePair of(L left, R right) { + return new ImmutablePair(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; + } + + /** + *

Throws {@code UnsupportedOperationException}.

+ * + *

This pair is immutable, so this operation is not supported.

+ * + * @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; + +/** + *

A mutable pair consisting of two {@code Object} elements.

+ * + *

Not #ThreadSafe#

+ * + * @param the left element type + * @param the right element type + * + * @since Lang 3.0 + * @version $Id: MutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $ + */ +public class MutablePair extends Pair { + + /** Serialization version */ + private static final long serialVersionUID = 4954918890077093841L; + + /** Left object */ + public L left; + /** Right object */ + public R right; + + /** + *

Obtains an immutable pair of from two objects inferring the generic types.

+ * + *

This factory allows the pair to be created using inference to + * obtain the generic types.

+ * + * @param the left element type + * @param 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 MutablePair of(L left, R right) { + return new MutablePair(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; + +/** + *

A pair consisting of two elements.

+ * + *

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'.

+ * + *

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.

+ * + * @param the left element type + * @param the right element type + * + * @since Lang 3.0 + * @version $Id: Pair.java 1142401 2011-07-03 08:30:12Z bayard $ + */ +public abstract class Pair implements Map.Entry, Comparable>, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 4954918890077093841L; + + /** + *

Obtains an immutable pair of from two objects inferring the generic types.

+ * + *

This factory allows the pair to be created using inference to + * obtain the generic types.

+ * + * @param the left element type + * @param 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 Pair of(L left, R right) { + return new ImmutablePair(left, right); + } + + //----------------------------------------------------------------------- + /** + *

Gets the left element from this pair.

+ * + *

When treated as a key-value pair, this is the key.

+ * + * @return the left element, may be null + */ + public abstract L getLeft(); + + /** + *

Gets the right element from this pair.

+ * + *

When treated as a key-value pair, this is the value.

+ * + * @return the right element, may be null + */ + public abstract R getRight(); + + /** + *

Gets the key from this pair.

+ * + *

This method implements the {@code Map.Entry} interface returning the + * left element as the key.

+ * + * @return the left element as the key, may be null + */ + public final L getKey() { + return getLeft(); + } + + /** + *

Gets the value from this pair.

+ * + *

This method implements the {@code Map.Entry} interface returning the + * right element as the value.

+ * + * @return the right element as the value, may be null + */ + public R getValue() { + return getRight(); + } + + //----------------------------------------------------------------------- + /** + *

Compares the pair based on the left element followed by the right element. + * The types must be {@code Comparable}.

+ * + * @param other the other pair, not null + * @return negative if this is less, zero if equal, positive if greater + */ + public int compareTo(Pair other) { + return new CompareToBuilder().append(getLeft(), other.getLeft()) + .append(getRight(), other.getRight()).toComparison(); + } + + /** + *

Compares this pair to another based on the two elements.

+ * + * @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; + } + + /** + *

Returns a suitable hash code. + * The hash code follows the definition in {@code Map.Entry}.

+ * + * @return the hash code + */ + @Override + public int hashCode() { + // see Map.Entry API specification + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); + } + + /** + *

Returns a String representation of this pair using the format {@code ($left,$right)}.

+ * + * @return a string describing this object, not null + */ + @Override + public String toString() { + return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString(); + } + + /** + *

Formats the receiver using the given format.

+ * + *

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)}.

+ * + * @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()); + } + +} -- cgit v1.1 From 560ce54b9727e4fb5e514716b33b70352f3a0ff1 Mon Sep 17 00:00:00 2001 From: Gerald Barker Date: Fri, 2 Sep 2011 23:29:31 +0100 Subject: Missed in last commit --- src/org/apache/commons/lang3/text/WordUtils.java | 497 +++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 src/org/apache/commons/lang3/text/WordUtils.java (limited to 'src/org/apache/commons') diff --git a/src/org/apache/commons/lang3/text/WordUtils.java b/src/org/apache/commons/lang3/text/WordUtils.java new file mode 100644 index 0000000..e8ffaa2 --- /dev/null +++ b/src/org/apache/commons/lang3/text/WordUtils.java @@ -0,0 +1,497 @@ +/* + * 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.text; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; + +/** + *

Operations on Strings that contain words.

+ * + *

This class tries to handle null input gracefully. + * An exception will not be thrown for a null input. + * Each method documents its behaviour in more detail.

+ * + * @since 2.0 + * @version $Id: WordUtils.java 1148520 2011-07-19 20:53:23Z ggregory $ + */ +public class WordUtils { + + /** + *

WordUtils instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * WordUtils.wrap("foo bar", 20);.

+ * + *

This constructor is public to permit tools that require a JavaBean + * instance to operate.

+ */ + public WordUtils() { + super(); + } + + // Wrapping + //-------------------------------------------------------------------------- + /** + *

Wraps a single line of text, identifying words by ' '.

+ * + *

New lines will be separated by the system property line separator. + * Very long words, such as URLs will not be wrapped.

+ * + *

Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

+ * + *
+     * WordUtils.wrap(null, *) = null
+     * WordUtils.wrap("", *) = ""
+     * 
+ * + * @param str the String to be word wrapped, may be null + * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 + * @return a line with newlines inserted, null if null input + */ + public static String wrap(String str, int wrapLength) { + return wrap(str, wrapLength, null, false); + } + + /** + *

Wraps a single line of text, identifying words by ' '.

+ * + *

Leading spaces on a new line are stripped. + * Trailing spaces are not stripped.

+ * + *
+     * WordUtils.wrap(null, *, *, *) = null
+     * WordUtils.wrap("", *, *, *) = ""
+     * 
+ * + * @param str the String to be word wrapped, may be null + * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 + * @param newLineStr the string to insert for a new line, + * null uses the system property line separator + * @param wrapLongWords true if long words (such as URLs) should be wrapped + * @return a line with newlines inserted, null if null input + */ + public static String wrap(String str, int wrapLength, String newLineStr, boolean wrapLongWords) { + if (str == null) { + return null; + } + if (newLineStr == null) { + newLineStr = SystemUtils.LINE_SEPARATOR; + } + if (wrapLength < 1) { + wrapLength = 1; + } + int inputLineLength = str.length(); + int offset = 0; + StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); + + while ((inputLineLength - offset) > wrapLength) { + if (str.charAt(offset) == ' ') { + offset++; + continue; + } + int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset); + + if (spaceToWrapAt >= offset) { + // normal case + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + + } else { + // really long word or URL + if (wrapLongWords) { + // wrap really long word one line at a time + wrappedLine.append(str.substring(offset, wrapLength + offset)); + wrappedLine.append(newLineStr); + offset += wrapLength; + } else { + // do not wrap really long word, just extend beyond limit + spaceToWrapAt = str.indexOf(' ', wrapLength + offset); + if (spaceToWrapAt >= 0) { + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str.substring(offset)); + offset = inputLineLength; + } + } + } + } + + // Whatever is left in line is short enough to just pass through + wrappedLine.append(str.substring(offset)); + + return wrappedLine.toString(); + } + + // Capitalizing + //----------------------------------------------------------------------- + /** + *

Capitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String)}.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null)        = null
+     * WordUtils.capitalize("")          = ""
+     * WordUtils.capitalize("i am FINE") = "I Am FINE"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + */ + public static String capitalize(String str) { + return capitalize(str, null); + } + + /** + *

Capitalizes all the delimiter separated words in a String. + * Only the first letter of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String, char[])}.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalize(null, *)            = null
+     * WordUtils.capitalize("", *)              = ""
+     * WordUtils.capitalize(*, new char[0])     = *
+     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
+     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + * @since 2.1 + */ + public static String capitalize(String str, char... delimiters) { + int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + capitalizeNext = true; + } else if (capitalizeNext) { + buffer[i] = Character.toTitleCase(ch); + capitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Converts all the whitespace separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null)        = null
+     * WordUtils.capitalizeFully("")          = ""
+     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return capitalized String, null if null String input + */ + public static String capitalizeFully(String str) { + return capitalizeFully(str, null); + } + + /** + *

Converts all the delimiter separated words in a String into capitalized words, + * that is each word is made up of a titlecase character and then a series of + * lowercase characters.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be capitalized.

+ * + *

A null input String returns null. + * Capitalization uses the Unicode title case, normally equivalent to + * upper case.

+ * + *
+     * WordUtils.capitalizeFully(null, *)            = null
+     * WordUtils.capitalizeFully("", *)              = ""
+     * WordUtils.capitalizeFully(*, null)            = *
+     * WordUtils.capitalizeFully(*, new char[0])     = *
+     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
+     * 
+ * + * @param str the String to capitalize, may be null + * @param delimiters set of characters to determine capitalization, null means whitespace + * @return capitalized String, null if null String input + * @since 2.1 + */ + public static String capitalizeFully(String str, char... delimiters) { + int delimLen = (delimiters == null ? -1 : delimiters.length); + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + str = str.toLowerCase(); + return capitalize(str, delimiters); + } + + //----------------------------------------------------------------------- + /** + *

Uncapitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

+ * + *
+     * WordUtils.uncapitalize(null)        = null
+     * WordUtils.uncapitalize("")          = ""
+     * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @return uncapitalized String, null if null String input + * @see #capitalize(String) + */ + public static String uncapitalize(String str) { + return uncapitalize(str, null); + } + + /** + *

Uncapitalizes all the whitespace separated words in a String. + * Only the first letter of each word is changed.

+ * + *

The delimiters represent a set of characters understood to separate words. + * The first string character and the first non-delimiter character after a + * delimiter will be uncapitalized.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

+ * + *
+     * WordUtils.uncapitalize(null, *)            = null
+     * WordUtils.uncapitalize("", *)              = ""
+     * WordUtils.uncapitalize(*, null)            = *
+     * WordUtils.uncapitalize(*, new char[0])     = *
+     * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
+     * 
+ * + * @param str the String to uncapitalize, may be null + * @param delimiters set of characters to determine uncapitalization, null means whitespace + * @return uncapitalized String, null if null String input + * @see #capitalize(String) + * @since 2.1 + */ + public static String uncapitalize(String str, char... delimiters) { + int delimLen = (delimiters == null ? -1 : delimiters.length); + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + char[] buffer = str.toCharArray(); + boolean uncapitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + uncapitalizeNext = true; + } else if (uncapitalizeNext) { + buffer[i] = Character.toLowerCase(ch); + uncapitalizeNext = false; + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Swaps the case of a String using a word based algorithm.

+ * + *
    + *
  • Upper case character converts to Lower case
  • + *
  • Title case character converts to Lower case
  • + *
  • Lower case character after Whitespace or at start converts to Title case
  • + *
  • Other Lower case character converts to Upper case
  • + *
+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

+ * + *
+     * StringUtils.swapCase(null)                 = null
+     * StringUtils.swapCase("")                   = ""
+     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+     * 
+ * + * @param str the String to swap case, may be null + * @return the changed String, null if null String input + */ + public static String swapCase(String str) { + if (StringUtils.isEmpty(str)) { + return str; + } + char[] buffer = str.toCharArray(); + + boolean whitespace = true; + + for (int i = 0; i < buffer.length; i++) { + char ch = buffer[i]; + if (Character.isUpperCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + whitespace = false; + } else if (Character.isTitleCase(ch)) { + buffer[i] = Character.toLowerCase(ch); + whitespace = false; + } else if (Character.isLowerCase(ch)) { + if (whitespace) { + buffer[i] = Character.toTitleCase(ch); + whitespace = false; + } else { + buffer[i] = Character.toUpperCase(ch); + } + } else { + whitespace = Character.isWhitespace(ch); + } + } + return new String(buffer); + } + + //----------------------------------------------------------------------- + /** + *

Extracts the initial letters from each word in the String.

+ * + *

The first letter of the string and all first letters after + * whitespace are returned as a new string. + * Their case is not changed.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null.

+ * + *
+     * WordUtils.initials(null)             = null
+     * WordUtils.initials("")               = ""
+     * WordUtils.initials("Ben John Lee")   = "BJL"
+     * WordUtils.initials("Ben J.Lee")      = "BJ"
+     * 
+ * + * @param str the String to get initials from, may be null + * @return String of initial letters, null if null String input + * @see #initials(String,char[]) + * @since 2.2 + */ + public static String initials(String str) { + return initials(str, null); + } + + /** + *

Extracts the initial letters from each word in the String.

+ * + *

The first letter of the string and all first letters after the + * defined delimiters are returned as a new string. + * Their case is not changed.

+ * + *

If the delimiters array is null, then Whitespace is used. + * Whitespace is defined by {@link Character#isWhitespace(char)}. + * A null input String returns null. + * An empty delimiter array returns an empty String.

+ * + *
+     * WordUtils.initials(null, *)                = null
+     * WordUtils.initials("", *)                  = ""
+     * WordUtils.initials("Ben John Lee", null)   = "BJL"
+     * WordUtils.initials("Ben J.Lee", null)      = "BJ"
+     * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
+     * WordUtils.initials(*, new char[0])         = ""
+     * 
+ * + * @param str the String to get initials from, may be null + * @param delimiters set of characters to determine words, null means whitespace + * @return String of initial letters, null if null String input + * @see #initials(String) + * @since 2.2 + */ + public static String initials(String str, char... delimiters) { + if (StringUtils.isEmpty(str)) { + return str; + } + if (delimiters != null && delimiters.length == 0) { + return ""; + } + int strLen = str.length(); + char[] buf = new char[strLen / 2 + 1]; + int count = 0; + boolean lastWasGap = true; + for (int i = 0; i < strLen; i++) { + char ch = str.charAt(i); + + if (isDelimiter(ch, delimiters)) { + lastWasGap = true; + } else if (lastWasGap) { + buf[count++] = ch; + lastWasGap = false; + } else { + continue; // ignore ch + } + } + return new String(buf, 0, count); + } + + //----------------------------------------------------------------------- + /** + * Is the character a delimiter. + * + * @param ch the character to check + * @param delimiters the delimiters + * @return true if it is a delimiter + */ + private static boolean isDelimiter(char ch, char[] delimiters) { + if (delimiters == null) { + return Character.isWhitespace(ch); + } + for (char delimiter : delimiters) { + if (ch == delimiter) { + return true; + } + } + return false; + } + +} -- cgit v1.1 From 8bbc46b9514f49ebf4b9533688ca296046563ef9 Mon Sep 17 00:00:00 2001 From: Gerald Barker Date: Sat, 3 Sep 2011 15:07:13 +0100 Subject: Remove Commons time package as we are using our own implementation --- .../apache/commons/lang3/time/DateFormatUtils.java | 320 ---- src/org/apache/commons/lang3/time/DateUtils.java | 1831 -------------------- .../commons/lang3/time/DurationFormatUtils.java | 662 ------- .../apache/commons/lang3/time/FastDateFormat.java | 1519 ---------------- src/org/apache/commons/lang3/time/FormatCache.java | 202 --- src/org/apache/commons/lang3/time/StopWatch.java | 382 ---- 6 files changed, 4916 deletions(-) delete mode 100644 src/org/apache/commons/lang3/time/DateFormatUtils.java delete mode 100644 src/org/apache/commons/lang3/time/DateUtils.java delete mode 100644 src/org/apache/commons/lang3/time/DurationFormatUtils.java delete mode 100644 src/org/apache/commons/lang3/time/FastDateFormat.java delete mode 100644 src/org/apache/commons/lang3/time/FormatCache.java delete mode 100644 src/org/apache/commons/lang3/time/StopWatch.java (limited to 'src/org/apache/commons') diff --git a/src/org/apache/commons/lang3/time/DateFormatUtils.java b/src/org/apache/commons/lang3/time/DateFormatUtils.java deleted file mode 100644 index b3b6541..0000000 --- a/src/org/apache/commons/lang3/time/DateFormatUtils.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * 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; - -/** - *

Date and time formatting utilities and constants.

- * - *

Formatting is performed using the thread-safe - * {@link org.apache.commons.lang3.time.FastDateFormat} class.

- * - * @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 yyyy-MM-dd'T'HH:mm:ss. - */ - 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 yyyy-MM-dd'T'HH:mm:ssZZ. - */ - 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 yyyy-MM-dd. - */ - public static final FastDateFormat ISO_DATE_FORMAT - = FastDateFormat.getInstance("yyyy-MM-dd"); - - /** - * ISO8601-like formatter for date with time zone. - * The format used is yyyy-MM-ddZZ. - * 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 'T'HH:mm:ss. - */ - public static final FastDateFormat ISO_TIME_FORMAT - = FastDateFormat.getInstance("'T'HH:mm:ss"); - - /** - * ISO8601 formatter for time with time zone. - * The format used is 'T'HH:mm:ssZZ. - */ - 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 HH:mm:ss. - * 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 HH:mm:ssZZ. - * 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 EEE, dd MMM yyyy HH:mm:ss Z in US locale. - */ - public static final FastDateFormat SMTP_DATETIME_FORMAT - = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); - - //----------------------------------------------------------------------- - /** - *

DateFormatUtils instances should NOT be constructed in standard programming.

- * - *

This constructor is public to permit tools that require a JavaBean instance - * to operate.

- */ - public DateFormatUtils() { - super(); - } - - /** - *

Formats a date/time into a specific pattern using the UTC time zone.

- * - * @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); - } - - /** - *

Formats a date/time into a specific pattern using the UTC time zone.

- * - * @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); - } - - /** - *

Formats a date/time into a specific pattern using the UTC time zone.

- * - * @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 null - * @return the formatted date - */ - public static String formatUTC(long millis, String pattern, Locale locale) { - return format(new Date(millis), pattern, UTC_TIME_ZONE, locale); - } - - /** - *

Formats a date/time into a specific pattern using the UTC time zone.

- * - * @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 null - * @return the formatted date - */ - public static String formatUTC(Date date, String pattern, Locale locale) { - return format(date, pattern, UTC_TIME_ZONE, locale); - } - - /** - *

Formats a date/time into a specific pattern.

- * - * @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); - } - - /** - *

Formats a date/time into a specific pattern.

- * - * @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); - } - - /** - *

Formats a calendar into a specific pattern.

- * - * @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); - } - - /** - *

Formats a date/time into a specific pattern in a time zone.

- * - * @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 null - * @return the formatted date - */ - public static String format(long millis, String pattern, TimeZone timeZone) { - return format(new Date(millis), pattern, timeZone, null); - } - - /** - *

Formats a date/time into a specific pattern in a time zone.

- * - * @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 null - * @return the formatted date - */ - public static String format(Date date, String pattern, TimeZone timeZone) { - return format(date, pattern, timeZone, null); - } - - /** - *

Formats a calendar into a specific pattern in a time zone.

- * - * @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 null - * @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); - } - - /** - *

Formats a date/time into a specific pattern in a locale.

- * - * @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 null - * @return the formatted date - */ - public static String format(long millis, String pattern, Locale locale) { - return format(new Date(millis), pattern, null, locale); - } - - /** - *

Formats a date/time into a specific pattern in a locale.

- * - * @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 null - * @return the formatted date - */ - public static String format(Date date, String pattern, Locale locale) { - return format(date, pattern, null, locale); - } - - /** - *

Formats a calendar into a specific pattern in a locale.

- * - * @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 null - * @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); - } - - /** - *

Formats a date/time into a specific pattern in a time zone and locale.

- * - * @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 null - * @param locale the locale to use, may be null - * @return the formatted date - */ - public static String format(long millis, String pattern, TimeZone timeZone, Locale locale) { - return format(new Date(millis), pattern, timeZone, locale); - } - - /** - *

Formats a date/time into a specific pattern in a time zone and locale.

- * - * @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 null - * @param locale the locale to use, may be null - * @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); - } - - /** - *

Formats a calendar into a specific pattern in a time zone and locale.

- * - * @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 null - * @param locale the locale to use, may be null - * @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 deleted file mode 100644 index 578d3a3..0000000 --- a/src/org/apache/commons/lang3/time/DateUtils.java +++ /dev/null @@ -1,1831 +0,0 @@ -/* - * 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; - -/** - *

A suite of utilities surrounding the use of the - * {@link java.util.Calendar} and {@link java.util.Date} object.

- * - *

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. - *

- * - * @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; - - /** - *

{@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);}.

- * - *

This constructor is public to permit tools that require a JavaBean - * instance to operate.

- */ - public DateUtils() { - super(); - } - - //----------------------------------------------------------------------- - /** - *

Checks if two date objects are on the same day ignoring time.

- * - *

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. - *

- * - * @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 null - * @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); - } - - /** - *

Checks if two calendar objects are on the same day ignoring time.

- * - *

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. - *

- * - * @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 null - * @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)); - } - - //----------------------------------------------------------------------- - /** - *

Checks if two date objects represent the same instant in time.

- * - *

This method compares the long millisecond time of the two objects.

- * - * @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 null - * @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(); - } - - /** - *

Checks if two calendar objects represent the same instant in time.

- * - *

This method compares the long millisecond time of the two objects.

- * - * @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 null - * @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(); - } - - //----------------------------------------------------------------------- - /** - *

Checks if two calendar objects represent the same local time.

- * - *

This method compares the values of the fields of the two objects. - * In addition, both calendars must be the same of the same type.

- * - * @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 null - * @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()); - } - - //----------------------------------------------------------------------- - /** - *

Parses a string representing a date by trying a variety of different parsers.

- * - *

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.

- * 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); - } - - //----------------------------------------------------------------------- - /** - *

Parses a string representing a date by trying a variety of different parsers.

- * - *

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.

- * 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); - } - - /** - *

Parses a string representing a date by trying a variety of different parsers.

- * - *

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.

- * - * @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; - } - - //----------------------------------------------------------------------- - /** - *

Round this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - *

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: - *

    - *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • - *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • - *
- *

- * - * @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(); - } - - /** - *

Round this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - *

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: - *

    - *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • - *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • - *
- *

- * - * @param date the date to work with, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different rounded date, not null - * @throws IllegalArgumentException if the date is null - * @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; - } - - /** - *

Round this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - *

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: - *

    - *
  • March 30, 2003 01:10 rounds to March 30, 2003 01:00
  • - *
  • March 30, 2003 01:40 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:10 rounds to March 30, 2003 03:00
  • - *
  • March 30, 2003 02:40 rounds to March 30, 2003 04:00
  • - *
- *

- * - * @param date the date to work with, either {@code Date} or {@code Calendar}, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different rounded date, not null - * @throws IllegalArgumentException if the date is null - * @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); - } - } - - //----------------------------------------------------------------------- - /** - *

Truncate this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different truncated date, not null - * @throws IllegalArgumentException if the date is null - * @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(); - } - - /** - *

Truncate this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different truncated date, not null - * @throws IllegalArgumentException if the date is null - * @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; - } - - /** - *

Truncate this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, either {@code Date} or {@code Calendar}, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different truncated date, not null - * @throws IllegalArgumentException if the date is null - * @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); - } - } - - //----------------------------------------------------------------------- - /** - *

Ceil this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different ceil date, not null - * @throws IllegalArgumentException if the date is null - * @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(); - } - - /** - *

Ceil this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different ceil date, not null - * @throws IllegalArgumentException if the date is null - * @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; - } - - /** - *

Ceil this date, leaving the field specified as the most - * significant field.

- * - *

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.

- * - * @param date the date to work with, either {@code Date} or {@code Calendar}, not null - * @param field the field from {@code Calendar} or SEMI_MONTH - * @return the different ceil date, not null - * @throws IllegalArgumentException if the date is null - * @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()); - } - } - - //----------------------------------------------------------------------- - /** - *

Internal calculation method.

- * - * @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"); - - } - - //----------------------------------------------------------------------- - /** - *

This constructs an Iterator over each day in a date - * range defined by a focus date and range style.

- * - *

For instance, passing Thursday, July 4, 2002 and a - * RANGE_MONTH_SUNDAY will return an Iterator - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

- * - *

This method provides an iterator that returns Calendar objects. - * The days are progressed using {@link Calendar#add(int, int)}.

- * - * @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 null - * @throws IllegalArgumentException if the rangeStyle is invalid - */ - public static Iterator 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); - } - - /** - *

This constructs an Iterator over each day in a date - * range defined by a focus date and range style.

- * - *

For instance, passing Thursday, July 4, 2002 and a - * RANGE_MONTH_SUNDAY will return an Iterator - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

- * - *

This method provides an iterator that returns Calendar objects. - * The days are progressed using {@link Calendar#add(int, int)}.

- * - * @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 null - * @throws IllegalArgumentException if the rangeStyle is invalid - */ - public static Iterator 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); - } - - /** - *

This constructs an Iterator over each day in a date - * range defined by a focus date and range style.

- * - *

For instance, passing Thursday, July 4, 2002 and a - * RANGE_MONTH_SUNDAY will return an Iterator - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

- * - * @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 null - * @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); - } - } - - /** - *

Returns the number of milliseconds within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 (10*1000 + 538)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in milliseconds)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMilliseconds(Date date, int fragment) { - return getFragment(date, fragment, Calendar.MILLISECOND); - } - - /** - *

Returns the number of seconds within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 - * (equivalent to deprecated date.getSeconds())
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 - * (equivalent to deprecated date.getSeconds())
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110 - * (7*3600 + 15*60 + 10)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in seconds)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInSeconds(Date date, int fragment) { - return getFragment(date, fragment, Calendar.SECOND); - } - - /** - *

Returns the number of minutes within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 - * (equivalent to deprecated date.getMinutes())
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 - * (equivalent to deprecated date.getMinutes())
  • - *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in minutes)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMinutes(Date date, int fragment) { - return getFragment(date, fragment, Calendar.MINUTE); - } - - /** - *

Returns the number of hours within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7 - * (equivalent to deprecated date.getHours())
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7 - * (equivalent to deprecated date.getHours())
  • - *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in hours)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInHours(Date date, int fragment) { - return getFragment(date, fragment, Calendar.HOUR_OF_DAY); - } - - /** - *

Returns the number of days within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 28, 2008 with Calendar.MONTH as fragment will return 28 - * (equivalent to deprecated date.getDay())
  • - *
  • February 28, 2008 with Calendar.MONTH as fragment will return 28 - * (equivalent to deprecated date.getDay())
  • - *
  • January 28, 2008 with Calendar.YEAR as fragment will return 28
  • - *
  • February 28, 2008 with Calendar.YEAR as fragment will return 59
  • - *
  • January 28, 2008 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in days)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInDays(Date date, int fragment) { - return getFragment(date, fragment, Calendar.DAY_OF_YEAR); - } - - /** - *

Returns the number of milliseconds within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538 - * (equivalent to calendar.get(Calendar.MILLISECOND))
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538 - * (equivalent to calendar.get(Calendar.MILLISECOND))
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 - * (10*1000 + 538)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in milliseconds)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMilliseconds(Calendar calendar, int fragment) { - return getFragment(calendar, fragment, Calendar.MILLISECOND); - } - /** - *

Returns the number of seconds within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 - * (equivalent to calendar.get(Calendar.SECOND))
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10 - * (equivalent to calendar.get(Calendar.SECOND))
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110 - * (7*3600 + 15*60 + 10)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in seconds)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInSeconds(Calendar calendar, int fragment) { - return getFragment(calendar, fragment, Calendar.SECOND); - } - - /** - *

Returns the number of minutes within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 - * (equivalent to calendar.get(Calendar.MINUTES))
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15 - * (equivalent to calendar.get(Calendar.MINUTES))
  • - *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in minutes)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMinutes(Calendar calendar, int fragment) { - return getFragment(calendar, fragment, Calendar.MINUTE); - } - - /** - *

Returns the number of hours within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • 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))
  • - *
  • 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))
  • - *
  • January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7
  • - *
  • January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)
  • - *
  • January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in hours)
  • - *
- *

- * - * @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 null or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInHours(Calendar calendar, int fragment) { - return getFragment(calendar, fragment, Calendar.HOUR_OF_DAY); - } - - /** - *

Returns the number of days within the - * fragment. All datefields greater than the fragment will be ignored.

- * - *

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).

- * - *

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.

- * - *

- *

    - *
  • January 28, 2008 with Calendar.MONTH as fragment will return 28 - * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))
  • - *
  • February 28, 2008 with Calendar.MONTH as fragment will return 28 - * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))
  • - *
  • January 28, 2008 with Calendar.YEAR as fragment will return 28 - * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))
  • - *
  • February 28, 2008 with Calendar.YEAR as fragment will return 59 - * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))
  • - *
  • January 28, 2008 with Calendar.MILLISECOND as fragment will return 0 - * (a millisecond cannot be split in days)
  • - *
- *

- * - * @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 null 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 null 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 null 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 null - * @param cal2 the second calendar, not null - * @param field the field from {@code Calendar} - * @return true if equal; otherwise false - * @throws IllegalArgumentException if any argument is null - * @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 null - * @param date2 the second date, not null - * @param field the field from {@code Calendar} - * @return true if equal; otherwise false - * @throws IllegalArgumentException if any argument is null - * @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 null - * @param cal2 the second calendar, not null - * @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 null - * @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 null - * @param date2 the second date, not null - * @param field the field from Calendar - * @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 null - * @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; - } - - //----------------------------------------------------------------------- - /** - *

Date iterator.

- */ - static class DateIterator implements Iterator { - 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 true 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 deleted file mode 100644 index 9f8d622..0000000 --- a/src/org/apache/commons/lang3/time/DurationFormatUtils.java +++ /dev/null @@ -1,662 +0,0 @@ -/* - * 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; - -/** - *

Duration formatting utilities and constants. The following table describes the tokens - * used in the pattern language for formatting.

- * - * - * - * - * - * - * - * - * - *
characterduration element
yyears
Mmonths
ddays
Hhours
mminutes
sseconds
Smilliseconds
- * - * @since 2.1 - * @version $Id: DurationFormatUtils.java 1144993 2011-07-11 00:51:16Z ggregory $ - */ -public class DurationFormatUtils { - - /** - *

DurationFormatUtils instances should NOT be constructed in standard programming.

- * - *

This constructor is public to permit tools that require a JavaBean instance - * to operate.

- */ - public DurationFormatUtils() { - super(); - } - - /** - *

Pattern used with FastDateFormat and SimpleDateFormat - * for the ISO8601 period format used in durations.

- * - * @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'"; - - //----------------------------------------------------------------------- - /** - *

Formats the time gap as a string.

- * - *

The format used is ISO8601-like: - * H:m:s.S.

- * - * @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"); - } - - /** - *

Formats the time gap as a string.

- * - *

The format used is the ISO8601 period format.

- * - *

This method formats durations using the days and lower fields of the - * ISO format pattern, such as P7D6TH5M4.321S.

- * - * @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); - } - - /** - *

Formats the time gap as a string, using the specified format, and padding with zeros and - * using the default timezone.

- * - *

This method formats durations using the days and lower fields of the - * format pattern. Months and larger are not used.

- * - * @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); - } - - /** - *

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.

- * - *

This method formats durations using the days and lower fields of the - * format pattern. Months and larger are not used.

- * - * @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); - } - - /** - *

Formats an elapsed time into a plurialization correct string.

- * - *

This method formats durations using the days and lower fields of the - * format pattern. Months and larger are not used.

- * - * @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(); - } - - //----------------------------------------------------------------------- - /** - *

Formats the time gap as a string.

- * - *

The format used is the ISO8601 period format.

- * - * @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()); - } - - /** - *

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()); - } - - /** - *

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.

- * - *

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.

- * - *

For more control, the Joda-Time - * library is recommended.

- * - * @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); - } - - //----------------------------------------------------------------------- - /** - *

The internal method to do the formatting.

- * - * @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 list = new ArrayList(array.length); - - boolean inLiteral = false; - StringBuffer buffer = null; - Token previous = null; - int sz = array.length; - for(int i=0; itrue 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 true 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 deleted file mode 100644 index 3a60527..0000000 --- a/src/org/apache/commons/lang3/time/FastDateFormat.java +++ /dev/null @@ -1,1519 +0,0 @@ -/* - * 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; - -/** - *

FastDateFormat is a fast and thread-safe version of - * {@link java.text.SimpleDateFormat}.

- * - *

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. - *

- * - *

Only formatting is supported, but all patterns are compatible with - * SimpleDateFormat (except time zones and some year patterns - see below).

- * - *

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).

- * - *

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.

- * - *

Javadoc cites for the year pattern: For formatting, if the number of - * pattern letters is 2, the year is truncated to 2 digits; otherwise it is - * interpreted as a number. 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.

- * - * @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 cache= new FormatCache() { - @Override - protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) { - return new FastDateFormat(pattern, timeZone, locale); - } - }; - - private static ConcurrentMap cTimeZoneDisplayCache = - new ConcurrentHashMap(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; - - //----------------------------------------------------------------------- - /** - *

Gets a formatter instance using the default pattern in the - * default locale.

- * - * @return a date/time formatter - */ - public static FastDateFormat getInstance() { - return cache.getDateTimeInstance(SHORT, SHORT, null, null); - } - - /** - *

Gets a formatter instance using the specified pattern in the - * default locale.

- * - * @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); - } - - /** - *

Gets a formatter instance using the specified pattern and - * time zone.

- * - * @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); - } - - /** - *

Gets a formatter instance using the specified pattern and - * locale.

- * - * @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); - } - - /** - *

Gets a formatter instance using the specified pattern, time zone - * and locale.

- * - * @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); - } - - //----------------------------------------------------------------------- - /** - *

Gets a date formatter instance using the specified style in the - * default time zone and locale.

- * - * @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); - } - - /** - *

Gets a date formatter instance using the specified style and - * locale in the default time zone.

- * - * @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); - } - - /** - *

Gets a date formatter instance using the specified style and - * time zone in the default locale.

- * - * @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); - } - - /** - *

Gets a date formatter instance using the specified style, time - * zone and locale.

- * - * @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); - } - - //----------------------------------------------------------------------- - /** - *

Gets a time formatter instance using the specified style in the - * default time zone and locale.

- * - * @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); - } - - /** - *

Gets a time formatter instance using the specified style and - * locale in the default time zone.

- * - * @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); - } - - /** - *

Gets a time formatter instance using the specified style and - * time zone in the default locale.

- * - * @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); - } - - /** - *

Gets a time formatter instance using the specified style, time - * zone and locale.

- * - * @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); - } - - //----------------------------------------------------------------------- - /** - *

Gets a date/time formatter instance using the specified style - * in the default time zone and locale.

- * - * @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); - } - - /** - *

Gets a date/time formatter instance using the specified style and - * locale in the default time zone.

- * - * @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); - } - - /** - *

Gets a date/time formatter instance using the specified style and - * time zone in the default locale.

- * - * @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); - } - /** - *

Gets a date/time formatter instance using the specified style, - * time zone and locale.

- * - * @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); - } - - //----------------------------------------------------------------------- - /** - *

Gets the time zone display name, using a cache for performance.

- * - * @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 - //----------------------------------------------------------------------- - /** - *

Constructs a new FastDateFormat.

- * - * @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(); - } - - /** - *

Initializes the instance for first use.

- */ - private void init() { - List 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 - //----------------------------------------------------------------------- - /** - *

Returns a list of Rules given a pattern.

- * - * @return a {@code List} of Rule objects - * @throws IllegalArgumentException if pattern is invalid - */ - protected List parsePattern() { - DateFormatSymbols symbols = new DateFormatSymbols(mLocale); - List rules = new ArrayList(); - - 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; - } - - /** - *

Performs the parsing of tokens.

- * - * @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(); - } - - /** - *

Gets an appropriate rule for the padding required.

- * - * @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 - //----------------------------------------------------------------------- - /** - *

Formats a {@code Date}, {@code Calendar} or - * {@code Long} (milliseconds) object.

- * - * @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 ? "" : obj.getClass().getName())); - } - } - - /** - *

Formats a millisecond {@code long} value.

- * - * @param millis the millisecond value to format - * @return the formatted string - * @since 2.1 - */ - public String format(long millis) { - return format(new Date(millis)); - } - - /** - *

Formats a {@code Date} object using a {@code GregorianCalendar}.

- * - * @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(); - } - - /** - *

Formats a {@code Calendar} object.

- * - * @param calendar the calendar to format - * @return the formatted string - */ - public String format(Calendar calendar) { - return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString(); - } - - /** - *

Formats a milliseond {@code long} value into the - * supplied {@code StringBuffer}.

- * - * @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); - } - - /** - *

Formats a {@code Date} object into the - * supplied {@code StringBuffer} using a {@code GregorianCalendar}.

- * - * @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); - } - - /** - *

Formats a {@code Calendar} object into the - * supplied {@code StringBuffer}.

- * - * @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); - } - - /** - *

Performs the formatting by applying the rules to the - * specified calendar.

- * - * @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 - //----------------------------------------------------------------------- - /** - *

Parsing is not supported.

- * - * @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 - //----------------------------------------------------------------------- - /** - *

Gets the pattern used by this formatter.

- * - * @return the pattern, {@link java.text.SimpleDateFormat} compatible - */ - public String getPattern() { - return mPattern; - } - - /** - *

Gets the time zone used by this formatter.

- * - *

This zone is always used for {@code Date} formatting.

- * - * @return the time zone - */ - public TimeZone getTimeZone() { - return mTimeZone; - } - - /** - *

Gets the locale used by this formatter.

- * - * @return the locale - */ - public Locale getLocale() { - return mLocale; - } - - /** - *

Gets an estimate for the maximum string length that the - * formatter will produce.

- * - *

The actual formatted length will almost always be less than or - * equal to this amount.

- * - * @return the maximum formatted length - */ - public int getMaxLengthEstimate() { - return mMaxLengthEstimate; - } - - // Basics - //----------------------------------------------------------------------- - /** - *

Compares two objects for equality.

- * - * @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); - } - - /** - *

Returns a hashcode compatible with equals.

- * - * @return a hashcode compatible with equals - */ - @Override - public int hashCode() { - return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); - } - - /** - *

Gets a debugging string version of this formatter.

- * - * @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 - //----------------------------------------------------------------------- - /** - *

Inner class defining a rule.

- */ - 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); - } - - /** - *

Inner class defining a numeric rule.

- */ - 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); - } - - /** - *

Inner class to output a constant single character.

- */ - 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); - } - } - - /** - *

Inner class to output a constant string.

- */ - 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); - } - } - - /** - *

Inner class to output one of a set of values.

- */ - 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)]); - } - } - - /** - *

Inner class to output an unpadded number.

- */ - 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)); - } - } - } - - /** - *

Inner class to output an unpadded month.

- */ - 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')); - } - } - } - - /** - *

Inner class to output a padded number.

- */ - 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)); - } - } - } - - /** - *

Inner class to output a two digit number.

- */ - 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)); - } - } - } - - /** - *

Inner class to output a two digit year.

- */ - 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')); - } - } - - /** - *

Inner class to output a two digit month.

- */ - 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')); - } - } - - /** - *

Inner class to output the twelve hour field.

- */ - 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); - } - } - - /** - *

Inner class to output the twenty four hour field.

- */ - 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); - } - } - - /** - *

Inner class to output a time zone name.

- */ - 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); - } - } - } - - /** - *

Inner class to output a time zone as a number {@code +/-HHMM} - * or {@code +/-HH:MM}.

- */ - 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')); - } - } - - // ---------------------------------------------------------------------- - /** - *

Inner class that acts as a compound key for time zone names.

- */ - 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 deleted file mode 100644 index 19ee53a..0000000 --- a/src/org/apache/commons/lang3/time/FormatCache.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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; - -/** - *

FormatCache is a cache and factory for {@link Format}s.

- * - * @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 { - /** - * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG - */ - static final int NONE= -1; - - private final ConcurrentMap cInstanceCache - = new ConcurrentHashMap(7); - - private final ConcurrentMap cDateTimeInstanceCache - = new ConcurrentHashMap(7); - - /** - *

Gets a formatter instance using the default pattern in the - * default timezone and locale.

- * - * @return a date/time formatter - */ - public F getInstance() { - return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); - } - - /** - *

Gets a formatter instance using the specified pattern, time zone - * and locale.

- * - * @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 null - */ - 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; - } - - /** - *

Create a format instance using the specified pattern, time zone - * and locale.

- * - * @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 null - */ - abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); - - /** - *

Gets a date/time formatter instance using the specified style, - * time zone and locale.

- * - * @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); - } - - // ---------------------------------------------------------------------- - /** - *

Helper class to hold multi-part Map keys

- */ - private static class MultipartKey { - private final Object[] keys; - private int hashCode; - - /** - * Constructs an instance of MultipartKey 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 deleted file mode 100644 index f86ad85..0000000 --- a/src/org/apache/commons/lang3/time/StopWatch.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * 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; - -/** - *

- * StopWatch provides a convenient API for timings. - *

- * - *

- * To start the watch, call {@link #start()}. At this point you can: - *

- *
    - *
  • {@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.
  • - *
  • {@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.
  • - *
  • {@link #stop()} the watch to complete the timing session.
  • - *
- * - *

- * 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. - *

- * - *

- * 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. - *

- * - *

- * 1. split(), suspend(), or stop() cannot be invoked twice
- * 2. unsplit() may only be called if the watch has been split()
- * 3. resume() may only be called if the watch has been suspend()
- * 4. start() cannot be called twice without calling reset() - *

- * - *

This class is not thread-safe

- * - * @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; - - /** - *

- * Constructor. - *

- */ - public StopWatch() { - super(); - } - - /** - *

- * Start the stopwatch. - *

- * - *

- * This method starts a new timing session, clearing any previous values. - *

- * - * @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; - } - - /** - *

- * Stop the stopwatch. - *

- * - *

- * This method ends a new timing session, allowing the time to be retrieved. - *

- * - * @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; - } - - /** - *

- * Resets the stopwatch. Stops it if need be. - *

- * - *

- * This method clears the internal values to allow the object to be reused. - *

- */ - public void reset() { - this.runningState = STATE_UNSTARTED; - this.splitState = STATE_UNSPLIT; - } - - /** - *

- * Split the time. - *

- * - *

- * 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. - *

- * - * @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; - } - - /** - *

- * Remove a split. - *

- * - *

- * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to - * continue. - *

- * - * @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; - } - - /** - *

- * Suspend the stopwatch for later resumption. - *

- * - *

- * 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. - *

- * - * @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; - } - - /** - *

- * Resume the stopwatch after a suspend. - *

- * - *

- * 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. - *

- * - * @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; - } - - /** - *

- * Get the time on the stopwatch. - *

- * - *

- * This is either the time between the start and the moment this method is called, or the amount of time between - * start and stop. - *

- * - * @return the time in milliseconds - */ - public long getTime() { - return getNanoTime() / NANO_2_MILLIS; - } - /** - *

- * Get the time on the stopwatch in nanoseconds. - *

- * - *

- * This is either the time between the start and the moment this method is called, or the amount of time between - * start and stop. - *

- * - * @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. "); - } - - /** - *

- * Get the split time on the stopwatch. - *

- * - *

- * This is the time between start and latest split. - *

- * - * @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; - } - /** - *

- * Get the split time on the stopwatch in nanoseconds. - *

- * - *

- * This is the time between start and latest split. - *

- * - * @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; - } - - /** - *

- * Gets a summary of the time that the stopwatch recorded as a string. - *

- * - *

- * The format used is ISO8601-like, hours:minutes:seconds.milliseconds. - *

- * - * @return the time as a String - */ - @Override - public String toString() { - return DurationFormatUtils.formatDurationHMS(getTime()); - } - - /** - *

- * Gets a summary of the split time that the stopwatch recorded as a string. - *

- * - *

- * The format used is ISO8601-like, hours:minutes:seconds.milliseconds. - *

- * - * @return the split time as a String - * @since 2.1 - */ - public String toSplitString() { - return DurationFormatUtils.formatDurationHMS(getSplitTime()); - } - -} -- cgit v1.1 From 5d6cd5f76c3682f70e4fa18182849fb2110a6aca Mon Sep 17 00:00:00 2001 From: Gerald Barker Date: Sat, 3 Sep 2011 18:21:05 +0100 Subject: Remove @Deprecated method's from Commons code, Remove WordUtils as i'm not using it in my refactor --- src/org/apache/commons/lang3/CharUtils.java | 21 - src/org/apache/commons/lang3/StringUtils.java | 32 -- .../lang3/exception/DefaultExceptionContext.java | 2 +- src/org/apache/commons/lang3/text/WordUtils.java | 497 --------------------- 4 files changed, 1 insertion(+), 551 deletions(-) delete mode 100644 src/org/apache/commons/lang3/text/WordUtils.java (limited to 'src/org/apache/commons') diff --git a/src/org/apache/commons/lang3/CharUtils.java b/src/org/apache/commons/lang3/CharUtils.java index 4a6f188..e839e9b 100644 --- a/src/org/apache/commons/lang3/CharUtils.java +++ b/src/org/apache/commons/lang3/CharUtils.java @@ -67,27 +67,6 @@ public class CharUtils { super(); } - //----------------------------------------------------------------------- - /** - *

Converts the character to a Character.

- * - *

For ASCII 7 bit characters, this uses a cache that will return the - * same Character object each time.

- * - *
-     *   CharUtils.toCharacterObject(' ')  = ' '
-     *   CharUtils.toCharacterObject('A')  = 'A'
-     * 
- * - * @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); - } - /** *

Converts the String to a Character using the first character, returning * null for empty Strings.

diff --git a/src/org/apache/commons/lang3/StringUtils.java b/src/org/apache/commons/lang3/StringUtils.java index 0960e1a..9ea5de4 100644 --- a/src/org/apache/commons/lang3/StringUtils.java +++ b/src/org/apache/commons/lang3/StringUtils.java @@ -4356,38 +4356,6 @@ public class StringUtils { return str.substring(0, lastIdx); } - /** - *

Removes {@code separator} from the end of - * {@code str} if it's there, otherwise leave it alone.

- * - *

NOTE: This method changed in version 2.0. - * It now more closely matches Perl chomp. - * For the previous behavior, use {@link #substringBeforeLast(String, String)}. - * This method uses {@link String#endsWith(String)}.

- * - *
-     * StringUtils.chomp(null, *)         = null
-     * StringUtils.chomp("", *)           = ""
-     * StringUtils.chomp("foobar", "bar") = "foo"
-     * StringUtils.chomp("foobar", "baz") = "foobar"
-     * StringUtils.chomp("foo", "foo")    = ""
-     * StringUtils.chomp("foo ", "foo")   = "foo "
-     * StringUtils.chomp(" foo", "foo")   = " "
-     * StringUtils.chomp("foo", "foooo")  = "foo"
-     * StringUtils.chomp("foo", "")       = "foo"
-     * StringUtils.chomp("foo", null)     = "foo"
-     * 
- * - * @param str the String to chomp from, may be null - * @param separator separator String, may be null - * @return String without trailing separator, {@code null} if null String input - * @deprecated This feature will be removed in Lang 4.0 - */ - @Deprecated - public static String chomp(String str, String separator) { - return removeEnd(str,separator); - } - // Chopping //----------------------------------------------------------------------- /** diff --git a/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java index 3011f8b..b8128ec 100644 --- a/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java +++ b/src/org/apache/commons/lang3/exception/DefaultExceptionContext.java @@ -153,6 +153,6 @@ public class DefaultExceptionContext implements ExceptionContext, Serializable { buffer.append("---------------------------------"); } return buffer.toString(); - } + } } diff --git a/src/org/apache/commons/lang3/text/WordUtils.java b/src/org/apache/commons/lang3/text/WordUtils.java deleted file mode 100644 index e8ffaa2..0000000 --- a/src/org/apache/commons/lang3/text/WordUtils.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * 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.text; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; - -/** - *

Operations on Strings that contain words.

- * - *

This class tries to handle null input gracefully. - * An exception will not be thrown for a null input. - * Each method documents its behaviour in more detail.

- * - * @since 2.0 - * @version $Id: WordUtils.java 1148520 2011-07-19 20:53:23Z ggregory $ - */ -public class WordUtils { - - /** - *

WordUtils instances should NOT be constructed in - * standard programming. Instead, the class should be used as - * WordUtils.wrap("foo bar", 20);.

- * - *

This constructor is public to permit tools that require a JavaBean - * instance to operate.

- */ - public WordUtils() { - super(); - } - - // Wrapping - //-------------------------------------------------------------------------- - /** - *

Wraps a single line of text, identifying words by ' '.

- * - *

New lines will be separated by the system property line separator. - * Very long words, such as URLs will not be wrapped.

- * - *

Leading spaces on a new line are stripped. - * Trailing spaces are not stripped.

- * - *
-     * WordUtils.wrap(null, *) = null
-     * WordUtils.wrap("", *) = ""
-     * 
- * - * @param str the String to be word wrapped, may be null - * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 - * @return a line with newlines inserted, null if null input - */ - public static String wrap(String str, int wrapLength) { - return wrap(str, wrapLength, null, false); - } - - /** - *

Wraps a single line of text, identifying words by ' '.

- * - *

Leading spaces on a new line are stripped. - * Trailing spaces are not stripped.

- * - *
-     * WordUtils.wrap(null, *, *, *) = null
-     * WordUtils.wrap("", *, *, *) = ""
-     * 
- * - * @param str the String to be word wrapped, may be null - * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 - * @param newLineStr the string to insert for a new line, - * null uses the system property line separator - * @param wrapLongWords true if long words (such as URLs) should be wrapped - * @return a line with newlines inserted, null if null input - */ - public static String wrap(String str, int wrapLength, String newLineStr, boolean wrapLongWords) { - if (str == null) { - return null; - } - if (newLineStr == null) { - newLineStr = SystemUtils.LINE_SEPARATOR; - } - if (wrapLength < 1) { - wrapLength = 1; - } - int inputLineLength = str.length(); - int offset = 0; - StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); - - while ((inputLineLength - offset) > wrapLength) { - if (str.charAt(offset) == ' ') { - offset++; - continue; - } - int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset); - - if (spaceToWrapAt >= offset) { - // normal case - wrappedLine.append(str.substring(offset, spaceToWrapAt)); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - - } else { - // really long word or URL - if (wrapLongWords) { - // wrap really long word one line at a time - wrappedLine.append(str.substring(offset, wrapLength + offset)); - wrappedLine.append(newLineStr); - offset += wrapLength; - } else { - // do not wrap really long word, just extend beyond limit - spaceToWrapAt = str.indexOf(' ', wrapLength + offset); - if (spaceToWrapAt >= 0) { - wrappedLine.append(str.substring(offset, spaceToWrapAt)); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - } else { - wrappedLine.append(str.substring(offset)); - offset = inputLineLength; - } - } - } - } - - // Whatever is left in line is short enough to just pass through - wrappedLine.append(str.substring(offset)); - - return wrappedLine.toString(); - } - - // Capitalizing - //----------------------------------------------------------------------- - /** - *

Capitalizes all the whitespace separated words in a String. - * Only the first letter of each word is changed. To convert the - * rest of each word to lowercase at the same time, - * use {@link #capitalizeFully(String)}.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null. - * Capitalization uses the Unicode title case, normally equivalent to - * upper case.

- * - *
-     * WordUtils.capitalize(null)        = null
-     * WordUtils.capitalize("")          = ""
-     * WordUtils.capitalize("i am FINE") = "I Am FINE"
-     * 
- * - * @param str the String to capitalize, may be null - * @return capitalized String, null if null String input - * @see #uncapitalize(String) - * @see #capitalizeFully(String) - */ - public static String capitalize(String str) { - return capitalize(str, null); - } - - /** - *

Capitalizes all the delimiter separated words in a String. - * Only the first letter of each word is changed. To convert the - * rest of each word to lowercase at the same time, - * use {@link #capitalizeFully(String, char[])}.

- * - *

The delimiters represent a set of characters understood to separate words. - * The first string character and the first non-delimiter character after a - * delimiter will be capitalized.

- * - *

A null input String returns null. - * Capitalization uses the Unicode title case, normally equivalent to - * upper case.

- * - *
-     * WordUtils.capitalize(null, *)            = null
-     * WordUtils.capitalize("", *)              = ""
-     * WordUtils.capitalize(*, new char[0])     = *
-     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
-     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
-     * 
- * - * @param str the String to capitalize, may be null - * @param delimiters set of characters to determine capitalization, null means whitespace - * @return capitalized String, null if null String input - * @see #uncapitalize(String) - * @see #capitalizeFully(String) - * @since 2.1 - */ - public static String capitalize(String str, char... delimiters) { - int delimLen = delimiters == null ? -1 : delimiters.length; - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - char[] buffer = str.toCharArray(); - boolean capitalizeNext = true; - for (int i = 0; i < buffer.length; i++) { - char ch = buffer[i]; - if (isDelimiter(ch, delimiters)) { - capitalizeNext = true; - } else if (capitalizeNext) { - buffer[i] = Character.toTitleCase(ch); - capitalizeNext = false; - } - } - return new String(buffer); - } - - //----------------------------------------------------------------------- - /** - *

Converts all the whitespace separated words in a String into capitalized words, - * that is each word is made up of a titlecase character and then a series of - * lowercase characters.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null. - * Capitalization uses the Unicode title case, normally equivalent to - * upper case.

- * - *
-     * WordUtils.capitalizeFully(null)        = null
-     * WordUtils.capitalizeFully("")          = ""
-     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
-     * 
- * - * @param str the String to capitalize, may be null - * @return capitalized String, null if null String input - */ - public static String capitalizeFully(String str) { - return capitalizeFully(str, null); - } - - /** - *

Converts all the delimiter separated words in a String into capitalized words, - * that is each word is made up of a titlecase character and then a series of - * lowercase characters.

- * - *

The delimiters represent a set of characters understood to separate words. - * The first string character and the first non-delimiter character after a - * delimiter will be capitalized.

- * - *

A null input String returns null. - * Capitalization uses the Unicode title case, normally equivalent to - * upper case.

- * - *
-     * WordUtils.capitalizeFully(null, *)            = null
-     * WordUtils.capitalizeFully("", *)              = ""
-     * WordUtils.capitalizeFully(*, null)            = *
-     * WordUtils.capitalizeFully(*, new char[0])     = *
-     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
-     * 
- * - * @param str the String to capitalize, may be null - * @param delimiters set of characters to determine capitalization, null means whitespace - * @return capitalized String, null if null String input - * @since 2.1 - */ - public static String capitalizeFully(String str, char... delimiters) { - int delimLen = (delimiters == null ? -1 : delimiters.length); - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - str = str.toLowerCase(); - return capitalize(str, delimiters); - } - - //----------------------------------------------------------------------- - /** - *

Uncapitalizes all the whitespace separated words in a String. - * Only the first letter of each word is changed.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null.

- * - *
-     * WordUtils.uncapitalize(null)        = null
-     * WordUtils.uncapitalize("")          = ""
-     * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
-     * 
- * - * @param str the String to uncapitalize, may be null - * @return uncapitalized String, null if null String input - * @see #capitalize(String) - */ - public static String uncapitalize(String str) { - return uncapitalize(str, null); - } - - /** - *

Uncapitalizes all the whitespace separated words in a String. - * Only the first letter of each word is changed.

- * - *

The delimiters represent a set of characters understood to separate words. - * The first string character and the first non-delimiter character after a - * delimiter will be uncapitalized.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null.

- * - *
-     * WordUtils.uncapitalize(null, *)            = null
-     * WordUtils.uncapitalize("", *)              = ""
-     * WordUtils.uncapitalize(*, null)            = *
-     * WordUtils.uncapitalize(*, new char[0])     = *
-     * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
-     * 
- * - * @param str the String to uncapitalize, may be null - * @param delimiters set of characters to determine uncapitalization, null means whitespace - * @return uncapitalized String, null if null String input - * @see #capitalize(String) - * @since 2.1 - */ - public static String uncapitalize(String str, char... delimiters) { - int delimLen = (delimiters == null ? -1 : delimiters.length); - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - char[] buffer = str.toCharArray(); - boolean uncapitalizeNext = true; - for (int i = 0; i < buffer.length; i++) { - char ch = buffer[i]; - if (isDelimiter(ch, delimiters)) { - uncapitalizeNext = true; - } else if (uncapitalizeNext) { - buffer[i] = Character.toLowerCase(ch); - uncapitalizeNext = false; - } - } - return new String(buffer); - } - - //----------------------------------------------------------------------- - /** - *

Swaps the case of a String using a word based algorithm.

- * - *
    - *
  • Upper case character converts to Lower case
  • - *
  • Title case character converts to Lower case
  • - *
  • Lower case character after Whitespace or at start converts to Title case
  • - *
  • Other Lower case character converts to Upper case
  • - *
- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null.

- * - *
-     * StringUtils.swapCase(null)                 = null
-     * StringUtils.swapCase("")                   = ""
-     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
-     * 
- * - * @param str the String to swap case, may be null - * @return the changed String, null if null String input - */ - public static String swapCase(String str) { - if (StringUtils.isEmpty(str)) { - return str; - } - char[] buffer = str.toCharArray(); - - boolean whitespace = true; - - for (int i = 0; i < buffer.length; i++) { - char ch = buffer[i]; - if (Character.isUpperCase(ch)) { - buffer[i] = Character.toLowerCase(ch); - whitespace = false; - } else if (Character.isTitleCase(ch)) { - buffer[i] = Character.toLowerCase(ch); - whitespace = false; - } else if (Character.isLowerCase(ch)) { - if (whitespace) { - buffer[i] = Character.toTitleCase(ch); - whitespace = false; - } else { - buffer[i] = Character.toUpperCase(ch); - } - } else { - whitespace = Character.isWhitespace(ch); - } - } - return new String(buffer); - } - - //----------------------------------------------------------------------- - /** - *

Extracts the initial letters from each word in the String.

- * - *

The first letter of the string and all first letters after - * whitespace are returned as a new string. - * Their case is not changed.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null.

- * - *
-     * WordUtils.initials(null)             = null
-     * WordUtils.initials("")               = ""
-     * WordUtils.initials("Ben John Lee")   = "BJL"
-     * WordUtils.initials("Ben J.Lee")      = "BJ"
-     * 
- * - * @param str the String to get initials from, may be null - * @return String of initial letters, null if null String input - * @see #initials(String,char[]) - * @since 2.2 - */ - public static String initials(String str) { - return initials(str, null); - } - - /** - *

Extracts the initial letters from each word in the String.

- * - *

The first letter of the string and all first letters after the - * defined delimiters are returned as a new string. - * Their case is not changed.

- * - *

If the delimiters array is null, then Whitespace is used. - * Whitespace is defined by {@link Character#isWhitespace(char)}. - * A null input String returns null. - * An empty delimiter array returns an empty String.

- * - *
-     * WordUtils.initials(null, *)                = null
-     * WordUtils.initials("", *)                  = ""
-     * WordUtils.initials("Ben John Lee", null)   = "BJL"
-     * WordUtils.initials("Ben J.Lee", null)      = "BJ"
-     * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
-     * WordUtils.initials(*, new char[0])         = ""
-     * 
- * - * @param str the String to get initials from, may be null - * @param delimiters set of characters to determine words, null means whitespace - * @return String of initial letters, null if null String input - * @see #initials(String) - * @since 2.2 - */ - public static String initials(String str, char... delimiters) { - if (StringUtils.isEmpty(str)) { - return str; - } - if (delimiters != null && delimiters.length == 0) { - return ""; - } - int strLen = str.length(); - char[] buf = new char[strLen / 2 + 1]; - int count = 0; - boolean lastWasGap = true; - for (int i = 0; i < strLen; i++) { - char ch = str.charAt(i); - - if (isDelimiter(ch, delimiters)) { - lastWasGap = true; - } else if (lastWasGap) { - buf[count++] = ch; - lastWasGap = false; - } else { - continue; // ignore ch - } - } - return new String(buf, 0, count); - } - - //----------------------------------------------------------------------- - /** - * Is the character a delimiter. - * - * @param ch the character to check - * @param delimiters the delimiters - * @return true if it is a delimiter - */ - private static boolean isDelimiter(char ch, char[] delimiters) { - if (delimiters == null) { - return Character.isWhitespace(ch); - } - for (char delimiter : delimiters) { - if (ch == delimiter) { - return true; - } - } - return false; - } - -} -- cgit v1.1