From dc83c2820b96095f63d233475037ca7a9da2839f Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Fri, 3 Nov 2023 11:25:53 -0400 Subject: [PATCH] Sort main members --- .../apache/commons/lang3/AnnotationUtils.java | 266 +- .../org/apache/commons/lang3/ArchUtils.java | 76 +- .../org/apache/commons/lang3/BitField.java | 218 +- .../org/apache/commons/lang3/CharRange.java | 400 +- .../org/apache/commons/lang3/CharSet.java | 30 +- .../org/apache/commons/lang3/CharUtils.java | 382 +- .../org/apache/commons/lang3/ClassUtils.java | 20 +- .../org/apache/commons/lang3/Conversion.java | 1900 ++++---- .../org/apache/commons/lang3/JavaVersion.java | 138 +- .../org/apache/commons/lang3/LocaleUtils.java | 8 +- .../lang3/NotImplementedException.java | 36 +- .../org/apache/commons/lang3/ObjectUtils.java | 48 +- .../commons/lang3/SerializationException.java | 22 +- .../org/apache/commons/lang3/Streams.java | 416 +- .../commons/lang3/StringEscapeUtils.java | 482 +- .../org/apache/commons/lang3/Validate.java | 1906 ++++---- .../apache/commons/lang3/builder/Diff.java | 32 +- .../commons/lang3/builder/DiffBuilder.java | 272 +- .../commons/lang3/builder/DiffResult.java | 58 +- .../commons/lang3/builder/EqualsBuilder.java | 1492 +++--- .../apache/commons/lang3/builder/IDKey.java | 18 +- .../MultilineRecursiveToStringStyle.java | 160 +- .../lang3/builder/RecursiveToStringStyle.java | 36 +- .../lang3/builder/ReflectionDiffBuilder.java | 86 +- .../lang3/builder/StandardToStringStyle.java | 638 +-- .../lang3/builder/ToStringBuilder.java | 68 +- .../commons/lang3/builder/ToStringStyle.java | 3364 +++++++------- .../concurrent/AbstractCircuitBreaker.java | 210 +- .../AbstractConcurrentInitializer.java | 16 +- .../lang3/concurrent/AtomicInitializer.java | 22 +- .../concurrent/AtomicSafeInitializer.java | 28 +- .../concurrent/BackgroundInitializer.java | 306 +- .../lang3/concurrent/BasicThreadFactory.java | 334 +- .../CallableBackgroundInitializer.java | 26 +- .../lang3/concurrent/CircuitBreaker.java | 50 +- .../concurrent/CircuitBreakingException.java | 18 +- .../lang3/concurrent/ConcurrentException.java | 22 +- .../ConcurrentRuntimeException.java | 22 +- .../lang3/concurrent/ConcurrentUtils.java | 304 +- .../lang3/concurrent/ConstantInitializer.java | 90 +- .../concurrent/EventCountCircuitBreaker.java | 590 +-- .../commons/lang3/concurrent/FutureTasks.java | 8 +- .../lang3/concurrent/LazyInitializer.java | 16 +- .../MultiBackgroundInitializer.java | 338 +- .../concurrent/ThresholdCircuitBreaker.java | 18 +- .../lang3/concurrent/TimedSemaphore.java | 232 +- .../lang3/event/EventListenerSupport.java | 236 +- .../commons/lang3/event/EventUtils.java | 98 +- .../lang3/exception/CloneFailedException.java | 18 +- .../lang3/exception/ContextedException.java | 78 +- .../exception/ContextedRuntimeException.java | 78 +- .../commons/lang3/function/Consumers.java | 8 +- .../commons/lang3/function/Suppliers.java | 22 +- .../commons/lang3/function/TriFunction.java | 20 +- .../apache/commons/lang3/math/Fraction.java | 872 ++-- .../commons/lang3/math/IEEE754rUtils.java | 264 +- .../commons/lang3/math/NumberUtils.java | 2564 +++++------ .../commons/lang3/mutable/MutableBoolean.java | 142 +- .../commons/lang3/mutable/MutableByte.java | 338 +- .../commons/lang3/mutable/MutableDouble.java | 410 +- .../commons/lang3/mutable/MutableFloat.java | 408 +- .../commons/lang3/mutable/MutableInt.java | 330 +- .../commons/lang3/mutable/MutableLong.java | 330 +- .../commons/lang3/mutable/MutableObject.java | 40 +- .../commons/lang3/mutable/MutableShort.java | 396 +- .../lang3/reflect/ConstructorUtils.java | 204 +- .../commons/lang3/reflect/FieldUtils.java | 610 +-- .../lang3/reflect/InheritanceUtils.java | 22 +- .../commons/lang3/reflect/MemberUtils.java | 282 +- .../commons/lang3/reflect/MethodUtils.java | 1280 +++--- .../commons/lang3/reflect/TypeLiteral.java | 10 +- .../commons/lang3/text/CompositeFormat.java | 36 +- .../lang3/text/ExtendedMessageFormat.java | 362 +- .../commons/lang3/text/FormattableUtils.java | 80 +- .../apache/commons/lang3/text/StrBuilder.java | 4038 ++++++++--------- .../apache/commons/lang3/text/StrLookup.java | 170 +- .../apache/commons/lang3/text/StrMatcher.java | 546 +-- .../commons/lang3/text/StrSubstitutor.java | 924 ++-- .../commons/lang3/text/StrTokenizer.java | 940 ++-- .../apache/commons/lang3/text/WordUtils.java | 744 +-- .../translate/CharSequenceTranslator.java | 42 +- .../lang3/text/translate/EntityArrays.java | 160 +- .../text/translate/NumericEntityEscaper.java | 76 +- .../lang3/text/translate/OctalUnescaper.java | 36 +- .../lang3/text/translate/UnicodeEscaper.java | 98 +- .../lang3/time/AbstractFormatCache.java | 294 +- .../commons/lang3/time/DateFormatUtils.java | 18 +- .../apache/commons/lang3/time/DateParser.java | 54 +- .../commons/lang3/time/DatePrinter.java | 136 +- .../apache/commons/lang3/time/DateUtils.java | 2572 +++++------ .../lang3/time/DurationFormatUtils.java | 550 +-- .../commons/lang3/time/DurationUtils.java | 12 +- .../commons/lang3/time/FastDateFormat.java | 580 +-- .../commons/lang3/time/FastDateParser.java | 1492 +++--- .../commons/lang3/time/FastDatePrinter.java | 2314 +++++----- .../apache/commons/lang3/time/TimeZones.java | 8 +- 96 files changed, 20282 insertions(+), 20282 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java index 9d0571488..fcb579b1a 100644 --- a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java +++ b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java @@ -64,6 +64,17 @@ public class AnnotationUtils { setArrayEnd("]"); } + /** + * {@inheritDoc} + */ + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { + if (value instanceof Annotation) { + value = AnnotationUtils.toString((Annotation) value); + } + super.appendDetail(buffer, fieldName, value); + } + /** * {@inheritDoc} */ @@ -76,27 +87,99 @@ public class AnnotationUtils { // formatter:on } - /** - * {@inheritDoc} - */ - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { - if (value instanceof Annotation) { - value = AnnotationUtils.toString((Annotation) value); - } - super.appendDetail(buffer, fieldName, value); - } - }; /** - * {@link AnnotationUtils} instances should NOT be constructed in - * standard programming. Instead, the class should be used statically. + * Helper method for comparing two arrays of annotations. * - *

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

+ * @param a1 the first array + * @param a2 the second array + * @return a flag whether these arrays are equal */ - public AnnotationUtils() { + private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { + if (a1.length != a2.length) { + return false; + } + for (int i = 0; i < a1.length; i++) { + if (!equals(a1[i], a2[i])) { + return false; + } + } + return true; + } + + /** + * Helper method for comparing two objects of an array type. + * + * @param componentType the component type of the array + * @param o1 the first object + * @param o2 the second object + * @return a flag whether these objects are equal + */ + private static boolean arrayMemberEquals(final Class componentType, final Object o1, final Object o2) { + if (componentType.isAnnotation()) { + return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); + } + if (componentType.equals(Byte.TYPE)) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (componentType.equals(Short.TYPE)) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + if (componentType.equals(Integer.TYPE)) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (componentType.equals(Character.TYPE)) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (componentType.equals(Long.TYPE)) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (componentType.equals(Float.TYPE)) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (componentType.equals(Double.TYPE)) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (componentType.equals(Boolean.TYPE)) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + + /** + * Helper method for generating a hash code for an array. + * + * @param componentType the component type of the array + * @param o the array + * @return a hash code for the specified array + */ + private static int arrayMemberHash(final Class componentType, final Object o) { + if (componentType.equals(Byte.TYPE)) { + return Arrays.hashCode((byte[]) o); + } + if (componentType.equals(Short.TYPE)) { + return Arrays.hashCode((short[]) o); + } + if (componentType.equals(Integer.TYPE)) { + return Arrays.hashCode((int[]) o); + } + if (componentType.equals(Character.TYPE)) { + return Arrays.hashCode((char[]) o); + } + if (componentType.equals(Long.TYPE)) { + return Arrays.hashCode((long[]) o); + } + if (componentType.equals(Float.TYPE)) { + return Arrays.hashCode((float[]) o); + } + if (componentType.equals(Double.TYPE)) { + return Arrays.hashCode((double[]) o); + } + if (componentType.equals(Boolean.TYPE)) { + return Arrays.hashCode((boolean[]) o); + } + return Arrays.hashCode((Object[]) o); } /** @@ -170,27 +253,23 @@ public class AnnotationUtils { return result; } + //besides modularity, this has the advantage of autoboxing primitives: /** - * Generate a string representation of an Annotation, as suggested by - * {@link Annotation#toString()}. + * Helper method for generating a hash code for a member of an annotation. * - * @param a the annotation of which a string representation is desired - * @return the standard string representation of an annotation, not - * {@code null} + * @param name the name of the member + * @param value the value of the member + * @return a hash code for this member */ - public static String toString(final Annotation a) { - final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); - for (final Method m : a.annotationType().getDeclaredMethods()) { - if (m.getParameterTypes().length > 0) { - continue; // wtf? - } - try { - builder.append(m.getName(), m.invoke(a)); - } catch (final ReflectiveOperationException ex) { - throw new UncheckedException(ex); - } + private static int hashMember(final String name, final Object value) { + final int part1 = name.hashCode() * 127; + if (ObjectUtils.isArray(value)) { + return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); } - return builder.build(); + if (value instanceof Annotation) { + return part1 ^ hashCode((Annotation) value); + } + return part1 ^ value.hashCode(); } /** @@ -215,25 +294,6 @@ public class AnnotationUtils { || String.class.equals(type) || Class.class.equals(type); } - //besides modularity, this has the advantage of autoboxing primitives: - /** - * Helper method for generating a hash code for a member of an annotation. - * - * @param name the name of the member - * @param value the value of the member - * @return a hash code for this member - */ - private static int hashMember(final String name, final Object value) { - final int part1 = name.hashCode() * 127; - if (ObjectUtils.isArray(value)) { - return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); - } - if (value instanceof Annotation) { - return part1 ^ hashCode((Annotation) value); - } - return part1 ^ value.hashCode(); - } - /** * Helper method for checking whether two objects of the given type are * equal. This method is used to compare the parameters of two annotation @@ -261,95 +321,35 @@ public class AnnotationUtils { } /** - * Helper method for comparing two objects of an array type. + * Generate a string representation of an Annotation, as suggested by + * {@link Annotation#toString()}. * - * @param componentType the component type of the array - * @param o1 the first object - * @param o2 the second object - * @return a flag whether these objects are equal + * @param a the annotation of which a string representation is desired + * @return the standard string representation of an annotation, not + * {@code null} */ - private static boolean arrayMemberEquals(final Class componentType, final Object o1, final Object o2) { - if (componentType.isAnnotation()) { - return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); - } - if (componentType.equals(Byte.TYPE)) { - return Arrays.equals((byte[]) o1, (byte[]) o2); - } - if (componentType.equals(Short.TYPE)) { - return Arrays.equals((short[]) o1, (short[]) o2); - } - if (componentType.equals(Integer.TYPE)) { - return Arrays.equals((int[]) o1, (int[]) o2); - } - if (componentType.equals(Character.TYPE)) { - return Arrays.equals((char[]) o1, (char[]) o2); - } - if (componentType.equals(Long.TYPE)) { - return Arrays.equals((long[]) o1, (long[]) o2); - } - if (componentType.equals(Float.TYPE)) { - return Arrays.equals((float[]) o1, (float[]) o2); - } - if (componentType.equals(Double.TYPE)) { - return Arrays.equals((double[]) o1, (double[]) o2); - } - if (componentType.equals(Boolean.TYPE)) { - return Arrays.equals((boolean[]) o1, (boolean[]) o2); - } - return Arrays.equals((Object[]) o1, (Object[]) o2); - } - - /** - * Helper method for comparing two arrays of annotations. - * - * @param a1 the first array - * @param a2 the second array - * @return a flag whether these arrays are equal - */ - private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { - if (a1.length != a2.length) { - return false; - } - for (int i = 0; i < a1.length; i++) { - if (!equals(a1[i], a2[i])) { - return false; + public static String toString(final Annotation a) { + final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); + for (final Method m : a.annotationType().getDeclaredMethods()) { + if (m.getParameterTypes().length > 0) { + continue; // wtf? + } + try { + builder.append(m.getName(), m.invoke(a)); + } catch (final ReflectiveOperationException ex) { + throw new UncheckedException(ex); } } - return true; + return builder.build(); } /** - * Helper method for generating a hash code for an array. + * {@link AnnotationUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used statically. * - * @param componentType the component type of the array - * @param o the array - * @return a hash code for the specified array + *

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

*/ - private static int arrayMemberHash(final Class componentType, final Object o) { - if (componentType.equals(Byte.TYPE)) { - return Arrays.hashCode((byte[]) o); - } - if (componentType.equals(Short.TYPE)) { - return Arrays.hashCode((short[]) o); - } - if (componentType.equals(Integer.TYPE)) { - return Arrays.hashCode((int[]) o); - } - if (componentType.equals(Character.TYPE)) { - return Arrays.hashCode((char[]) o); - } - if (componentType.equals(Long.TYPE)) { - return Arrays.hashCode((long[]) o); - } - if (componentType.equals(Float.TYPE)) { - return Arrays.hashCode((float[]) o); - } - if (componentType.equals(Double.TYPE)) { - return Arrays.hashCode((double[]) o); - } - if (componentType.equals(Boolean.TYPE)) { - return Arrays.hashCode((boolean[]) o); - } - return Arrays.hashCode((Object[]) o); + public AnnotationUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/ArchUtils.java b/src/main/java/org/apache/commons/lang3/ArchUtils.java index efc5b0a18..4bc325c5e 100644 --- a/src/main/java/org/apache/commons/lang3/ArchUtils.java +++ b/src/main/java/org/apache/commons/lang3/ArchUtils.java @@ -39,44 +39,6 @@ public class ArchUtils { init(); } - private static void init() { - init_X86_32Bit(); - init_X86_64Bit(); - init_IA64_32Bit(); - init_IA64_64Bit(); - init_PPC_32Bit(); - init_PPC_64Bit(); - init_Aarch_64Bit(); - } - - private static void init_Aarch_64Bit() { - addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.AARCH_64), "aarch64"); - } - - private static void init_X86_32Bit() { - addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.X86), "x86", "i386", "i486", "i586", "i686", "pentium"); - } - - private static void init_X86_64Bit() { - addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.X86), "x86_64", "amd64", "em64t", "universal"); - } - - private static void init_IA64_32Bit() { - addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.IA_64), "ia64_32", "ia64n"); - } - - private static void init_IA64_64Bit() { - addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.IA_64), "ia64", "ia64w"); - } - - private static void init_PPC_32Bit() { - addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.PPC), "ppc", "power", "powerpc", "power_pc", "power_rs"); - } - - private static void init_PPC_64Bit() { - addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.PPC), "ppc64", "power64", "powerpc64", "power_pc64", "power_rs64"); - } - /** * Adds the given {@link Processor} with the given key {@link String} to the map. * @@ -126,4 +88,42 @@ public class ArchUtils { return ARCH_TO_PROCESSOR.get(value); } + private static void init() { + init_X86_32Bit(); + init_X86_64Bit(); + init_IA64_32Bit(); + init_IA64_64Bit(); + init_PPC_32Bit(); + init_PPC_64Bit(); + init_Aarch_64Bit(); + } + + private static void init_Aarch_64Bit() { + addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.AARCH_64), "aarch64"); + } + + private static void init_IA64_32Bit() { + addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.IA_64), "ia64_32", "ia64n"); + } + + private static void init_IA64_64Bit() { + addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.IA_64), "ia64", "ia64w"); + } + + private static void init_PPC_32Bit() { + addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.PPC), "ppc", "power", "powerpc", "power_pc", "power_rs"); + } + + private static void init_PPC_64Bit() { + addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.PPC), "ppc64", "power64", "powerpc64", "power_pc64", "power_rs64"); + } + + private static void init_X86_32Bit() { + addProcessors(new Processor(Processor.Arch.BIT_32, Processor.Type.X86), "x86", "i386", "i486", "i586", "i686", "pentium"); + } + + private static void init_X86_64Bit() { + addProcessors(new Processor(Processor.Arch.BIT_64, Processor.Type.X86), "x86_64", "amd64", "em64t", "universal"); + } + } diff --git a/src/main/java/org/apache/commons/lang3/BitField.java b/src/main/java/org/apache/commons/lang3/BitField.java index a2d0ccbe3..03f6339a3 100644 --- a/src/main/java/org/apache/commons/lang3/BitField.java +++ b/src/main/java/org/apache/commons/lang3/BitField.java @@ -88,39 +88,40 @@ public class BitField { } /** - * Obtains the value for the specified BitField, appropriately - * shifted right. + * Clears the bits. * - *

Many users of a BitField will want to treat the specified - * bits as an int value, and will not want to be aware that the - * value is stored as a BitField (and so shifted left so many - * bits).

- * - * @see #setValue(int,int) - * @param holder the int data containing the bits we're interested - * in - * @return the selected bits, shifted right appropriately + * @param holder the int data containing the bits we're + * interested in + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) */ - public int getValue(final int holder) { - return getRawValue(holder) >> shiftCount; + public int clear(final int holder) { + return holder & ~mask; } /** - * Obtains the value for the specified BitField, appropriately - * shifted right, as a short. + * Clears the bits. * - *

Many users of a BitField will want to treat the specified - * bits as an int value, and will not want to be aware that the - * value is stored as a BitField (and so shifted left so many - * bits).

+ * @param holder the byte data containing the bits we're + * interested in + * + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) + */ + public byte clearByte(final byte holder) { + return (byte) clear(holder); + } + + /** + * Clears the bits. * - * @see #setShortValue(short,short) * @param holder the short data containing the bits we're * interested in - * @return the selected bits, shifted right appropriately + * @return the value of holder with the specified bits cleared + * (set to {@code 0}) */ - public short getShortValue(final short holder) { - return (short) getValue(holder); + public short clearShort(final short holder) { + return (short) clear(holder); } /** @@ -146,20 +147,39 @@ public class BitField { } /** - * Returns whether the field is set or not. + * Obtains the value for the specified BitField, appropriately + * shifted right, as a short. * - *

This is most commonly used for a single-bit field, which is - * often used to represent a boolean value; the results of using - * it for a multi-bit field is to determine whether *any* of its - * bits are set.

+ *

Many users of a BitField will want to treat the specified + * bits as an int value, and will not want to be aware that the + * value is stored as a BitField (and so shifted left so many + * bits).

* + * @see #setShortValue(short,short) + * @param holder the short data containing the bits we're + * interested in + * @return the selected bits, shifted right appropriately + */ + public short getShortValue(final short holder) { + return (short) getValue(holder); + } + + /** + * Obtains the value for the specified BitField, appropriately + * shifted right. + * + *

Many users of a BitField will want to treat the specified + * bits as an int value, and will not want to be aware that the + * value is stored as a BitField (and so shifted left so many + * bits).

+ * + * @see #setValue(int,int) * @param holder the int data containing the bits we're interested * in - * @return {@code true} if any of the bits are set, - * else {@code false} + * @return the selected bits, shifted right appropriately */ - public boolean isSet(final int holder) { - return (holder & mask) != 0; + public int getValue(final int holder) { + return getRawValue(holder) >> shiftCount; } /** @@ -179,68 +199,20 @@ public class BitField { } /** - * Replaces the bits with new values. + * Returns whether the field is set or not. * - * @see #getValue(int) - * @param holder the int data containing the bits we're - * interested in - * @param value the new value for the specified bits - * @return the value of holder with the bits from the value - * parameter replacing the old bits + *

This is most commonly used for a single-bit field, which is + * often used to represent a boolean value; the results of using + * it for a multi-bit field is to determine whether *any* of its + * bits are set.

+ * + * @param holder the int data containing the bits we're interested + * in + * @return {@code true} if any of the bits are set, + * else {@code false} */ - public int setValue(final int holder, final int value) { - return (holder & ~mask) | ((value << shiftCount) & mask); - } - - /** - * Replaces the bits with new values. - * - * @see #getShortValue(short) - * @param holder the short data containing the bits we're - * interested in - * @param value the new value for the specified bits - * @return the value of holder with the bits from the value - * parameter replacing the old bits - */ - public short setShortValue(final short holder, final short value) { - return (short) setValue(holder, value); - } - - /** - * Clears the bits. - * - * @param holder the int data containing the bits we're - * interested in - * @return the value of holder with the specified bits cleared - * (set to {@code 0}) - */ - public int clear(final int holder) { - return holder & ~mask; - } - - /** - * Clears the bits. - * - * @param holder the short data containing the bits we're - * interested in - * @return the value of holder with the specified bits cleared - * (set to {@code 0}) - */ - public short clearShort(final short holder) { - return (short) clear(holder); - } - - /** - * Clears the bits. - * - * @param holder the byte data containing the bits we're - * interested in - * - * @return the value of holder with the specified bits cleared - * (set to {@code 0}) - */ - public byte clearByte(final byte holder) { - return (byte) clear(holder); + public boolean isSet(final int holder) { + return (holder & mask) != 0; } /** @@ -256,15 +228,16 @@ public class BitField { } /** - * Sets the bits. + * Sets a boolean BitField. * - * @param holder the short data containing the bits we're + * @param holder the int data containing the bits we're * interested in - * @return the value of holder with the specified bits set - * to {@code 1} + * @param flag indicating whether to set or clear the bits + * @return the value of holder with the specified bits set or + * cleared */ - public short setShort(final short holder) { - return (short) set(holder); + public int setBoolean(final int holder, final boolean flag) { + return flag ? set(holder) : clear(holder); } /** @@ -283,14 +256,26 @@ public class BitField { /** * Sets a boolean BitField. * - * @param holder the int data containing the bits we're + * @param holder the byte data containing the bits we're * interested in * @param flag indicating whether to set or clear the bits * @return the value of holder with the specified bits set or - * cleared + * cleared */ - public int setBoolean(final int holder, final boolean flag) { - return flag ? set(holder) : clear(holder); + public byte setByteBoolean(final byte holder, final boolean flag) { + return flag ? setByte(holder) : clearByte(holder); + } + + /** + * Sets the bits. + * + * @param holder the short data containing the bits we're + * interested in + * @return the value of holder with the specified bits set + * to {@code 1} + */ + public short setShort(final short holder) { + return (short) set(holder); } /** @@ -307,16 +292,31 @@ public class BitField { } /** - * Sets a boolean BitField. + * Replaces the bits with new values. * - * @param holder the byte data containing the bits we're + * @see #getShortValue(short) + * @param holder the short data containing the bits we're * interested in - * @param flag indicating whether to set or clear the bits - * @return the value of holder with the specified bits set or - * cleared + * @param value the new value for the specified bits + * @return the value of holder with the bits from the value + * parameter replacing the old bits */ - public byte setByteBoolean(final byte holder, final boolean flag) { - return flag ? setByte(holder) : clearByte(holder); + public short setShortValue(final short holder, final short value) { + return (short) setValue(holder, value); + } + + /** + * Replaces the bits with new values. + * + * @see #getValue(int) + * @param holder the int data containing the bits we're + * interested in + * @param value the new value for the specified bits + * @return the value of holder with the bits from the value + * parameter replacing the old bits + */ + public int setValue(final int holder, final int value) { + return (holder & ~mask) | ((value << shiftCount) & mask); } } diff --git a/src/main/java/org/apache/commons/lang3/CharRange.java b/src/main/java/org/apache/commons/lang3/CharRange.java index cf90e0950..ae593c219 100644 --- a/src/main/java/org/apache/commons/lang3/CharRange.java +++ b/src/main/java/org/apache/commons/lang3/CharRange.java @@ -33,6 +33,102 @@ import java.util.Objects; // to depend on Range. final class CharRange implements Iterable, Serializable { + /** + * Character {@link Iterator}. + *

#NotThreadSafe#

+ */ + private static final class CharacterIterator implements Iterator { + /** The current character */ + private char current; + + private final CharRange range; + private boolean hasNext; + + /** + * Constructs a new iterator for the character range. + * + * @param r The character range + */ + private CharacterIterator(final CharRange r) { + range = r; + hasNext = true; + + if (range.negated) { + if (range.start == 0) { + if (range.end == Character.MAX_VALUE) { + // This range is an empty set + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = 0; + } + } else { + current = range.start; + } + } + + /** + * Has the iterator not reached the end character yet? + * + * @return {@code true} if the iterator has yet to reach the character date + */ + @Override + public boolean hasNext() { + return hasNext; + } + + /** + * Returns the next character in the iteration + * + * @return {@link Character} for the next character + */ + @Override + public Character next() { + if (!hasNext) { + throw new NoSuchElementException(); + } + final char cur = current; + prepareNext(); + return Character.valueOf(cur); + } + + /** + * Prepares the next character in the range. + */ + private void prepareNext() { + if (range.negated) { + if (current == Character.MAX_VALUE) { + hasNext = false; + } else if (current + 1 == range.start) { + if (range.end == Character.MAX_VALUE) { + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = (char) (current + 1); + } + } else if (current < range.end) { + current = (char) (current + 1); + } else { + hasNext = false; + } + } + + /** + * Always throws UnsupportedOperationException. + * + * @throws UnsupportedOperationException Always thrown. + * @see java.util.Iterator#remove() + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + /** * Required for serialization support. Lang version 2.0. * @@ -40,6 +136,67 @@ final class CharRange implements Iterable, Serializable { */ private static final long serialVersionUID = 8270183163158333422L; + /** Empty array. */ + static final CharRange[] EMPTY_ARRAY = {}; + + /** + * Constructs a {@link CharRange} over a single character. + * + * @param ch only character in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange is(final char ch) { + return new CharRange(ch, ch, false); + } + + /** + * Constructs a {@link CharRange} over a set of characters. + * + *

If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isIn(final char start, final char end) { + return new CharRange(start, end, false); + } + + /** + * Constructs a negated {@link CharRange} over a single character. + * + *

A negated range includes everything except that defined by the + * single character.

+ * + * @param ch only character in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNot(final char ch) { + return new CharRange(ch, ch, true); + } + + /** + * Constructs a negated {@link CharRange} over a set of characters. + * + *

A negated range includes everything except that defined by the + * start and end characters.

+ * + *

If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNotIn(final char start, final char end) { + return new CharRange(start, end, true); + } + /** The first character, inclusive, in the range. */ private final char start; @@ -52,9 +209,6 @@ final class CharRange implements Iterable, Serializable { /** Cached toString. */ private transient String iToString; - /** Empty array. */ - static final CharRange[] EMPTY_ARRAY = {}; - /** * Constructs a {@link CharRange} over a set of characters, * optionally negating the range. @@ -81,95 +235,6 @@ final class CharRange implements Iterable, Serializable { this.negated = negated; } - /** - * Constructs a {@link CharRange} over a single character. - * - * @param ch only character in this range - * @return the new CharRange object - * @since 2.5 - */ - public static CharRange is(final char ch) { - return new CharRange(ch, ch, false); - } - - /** - * Constructs a negated {@link CharRange} over a single character. - * - *

A negated range includes everything except that defined by the - * single character.

- * - * @param ch only character in this range - * @return the new CharRange object - * @since 2.5 - */ - public static CharRange isNot(final char ch) { - return new CharRange(ch, ch, true); - } - - /** - * Constructs a {@link CharRange} over a set of characters. - * - *

If start and end are in the wrong order, they are reversed. - * Thus {@code a-e} is the same as {@code e-a}.

- * - * @param start first character, inclusive, in this range - * @param end last character, inclusive, in this range - * @return the new CharRange object - * @since 2.5 - */ - public static CharRange isIn(final char start, final char end) { - return new CharRange(start, end, false); - } - - /** - * Constructs a negated {@link CharRange} over a set of characters. - * - *

A negated range includes everything except that defined by the - * start and end characters.

- * - *

If start and end are in the wrong order, they are reversed. - * Thus {@code a-e} is the same as {@code e-a}.

- * - * @param start first character, inclusive, in this range - * @param end last character, inclusive, in this range - * @return the new CharRange object - * @since 2.5 - */ - public static CharRange isNotIn(final char start, final char end) { - return new CharRange(start, end, true); - } - - // Accessors - /** - * Gets the start character for this character range. - * - * @return the start char (inclusive) - */ - public char getStart() { - return this.start; - } - - /** - * Gets the end character for this character range. - * - * @return the end char (inclusive) - */ - public char getEnd() { - return this.end; - } - - /** - * Is this {@link CharRange} negated. - * - *

A negated range includes everything except that defined by the - * start and end characters.

- * - * @return {@code true} if negated - */ - public boolean isNegated() { - return negated; - } - // Contains /** * Is the character specified contained in this range. @@ -223,6 +288,25 @@ final class CharRange implements Iterable, Serializable { return start == other.start && end == other.end && negated == other.negated; } + /** + * Gets the end character for this character range. + * + * @return the end char (inclusive) + */ + public char getEnd() { + return this.end; + } + + // Accessors + /** + * Gets the start character for this character range. + * + * @return the start char (inclusive) + */ + public char getStart() { + return this.start; + } + /** * Gets a hashCode compatible with the equals method. * @@ -233,6 +317,30 @@ final class CharRange implements Iterable, Serializable { return 83 + start + 7 * end + (negated ? 1 : 0); } + /** + * Is this {@link CharRange} negated. + * + *

A negated range includes everything except that defined by the + * start and end characters.

+ * + * @return {@code true} if negated + */ + public boolean isNegated() { + return negated; + } + + /** + * Returns an iterator which can be used to walk through the characters described by this range. + * + *

#NotThreadSafe# the iterator is not thread-safe

+ * @return an iterator to the chars represented by this range + * @since 2.5 + */ + @Override + public Iterator iterator() { + return new CharacterIterator(this); + } + /** * Gets a string representation of the character range. * @@ -254,112 +362,4 @@ final class CharRange implements Iterable, Serializable { } return iToString; } - - /** - * Returns an iterator which can be used to walk through the characters described by this range. - * - *

#NotThreadSafe# the iterator is not thread-safe

- * @return an iterator to the chars represented by this range - * @since 2.5 - */ - @Override - public Iterator iterator() { - return new CharacterIterator(this); - } - - /** - * Character {@link Iterator}. - *

#NotThreadSafe#

- */ - private static final class CharacterIterator implements Iterator { - /** The current character */ - private char current; - - private final CharRange range; - private boolean hasNext; - - /** - * Constructs a new iterator for the character range. - * - * @param r The character range - */ - private CharacterIterator(final CharRange r) { - range = r; - hasNext = true; - - if (range.negated) { - if (range.start == 0) { - if (range.end == Character.MAX_VALUE) { - // This range is an empty set - hasNext = false; - } else { - current = (char) (range.end + 1); - } - } else { - current = 0; - } - } else { - current = range.start; - } - } - - /** - * Prepares the next character in the range. - */ - private void prepareNext() { - if (range.negated) { - if (current == Character.MAX_VALUE) { - hasNext = false; - } else if (current + 1 == range.start) { - if (range.end == Character.MAX_VALUE) { - hasNext = false; - } else { - current = (char) (range.end + 1); - } - } else { - current = (char) (current + 1); - } - } else if (current < range.end) { - current = (char) (current + 1); - } else { - hasNext = false; - } - } - - /** - * Has the iterator not reached the end character yet? - * - * @return {@code true} if the iterator has yet to reach the character date - */ - @Override - public boolean hasNext() { - return hasNext; - } - - /** - * Returns the next character in the iteration - * - * @return {@link Character} for the next character - */ - @Override - public Character next() { - if (!hasNext) { - throw new NoSuchElementException(); - } - final char cur = current; - prepareNext(); - return Character.valueOf(cur); - } - - /** - * Always throws UnsupportedOperationException. - * - * @throws UnsupportedOperationException Always thrown. - * @see java.util.Iterator#remove() - */ - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } } diff --git a/src/main/java/org/apache/commons/lang3/CharSet.java b/src/main/java/org/apache/commons/lang3/CharSet.java index a6b7bcbb5..80aa2ef17 100644 --- a/src/main/java/org/apache/commons/lang3/CharSet.java +++ b/src/main/java/org/apache/commons/lang3/CharSet.java @@ -88,9 +88,6 @@ public class CharSet implements Serializable { COMMON.put("0-9", ASCII_NUMERIC); } - /** The set of CharRange objects. */ - private final Set set = Collections.synchronizedSet(new HashSet<>()); - /** * Factory method to create a new CharSet using a special syntax. * @@ -165,6 +162,9 @@ public class CharSet implements Serializable { return new CharSet(setStrs); } + /** The set of CharRange objects. */ + private final Set set = Collections.synchronizedSet(new HashSet<>()); + /** * Constructs a new CharSet using the set syntax. * Each string is merged in with the set. @@ -210,18 +210,6 @@ public class CharSet implements Serializable { } } - /** - * Gets the internal set as an array of CharRange objects. - * - * @return an array of immutable CharRange objects - * @since 2.0 - */ -// NOTE: This is no longer public as CharRange is no longer a public class. -// It may be replaced when CharSet moves to Range. - /*public*/ CharRange[] getCharRanges() { - return set.toArray(CharRange.EMPTY_ARRAY); - } - /** * Does the {@link CharSet} contain the specified * character {@code ch}. @@ -259,6 +247,18 @@ public class CharSet implements Serializable { return set.equals(other.set); } + /** + * Gets the internal set as an array of CharRange objects. + * + * @return an array of immutable CharRange objects + * @since 2.0 + */ +// NOTE: This is no longer public as CharRange is no longer a public class. +// It may be replaced when CharSet moves to Range. + /*public*/ CharRange[] getCharRanges() { + return set.toArray(CharRange.EMPTY_ARRAY); + } + /** * Gets a hash code compatible with the equals method. * diff --git a/src/main/java/org/apache/commons/lang3/CharUtils.java b/src/main/java/org/apache/commons/lang3/CharUtils.java index 52b864d6e..b85366453 100644 --- a/src/main/java/org/apache/commons/lang3/CharUtils.java +++ b/src/main/java/org/apache/commons/lang3/CharUtils.java @@ -64,54 +64,169 @@ public class CharUtils { } /** - * {@link CharUtils} instances should NOT be constructed in standard programming. - * Instead, the class should be used as {@code CharUtils.toString('c');}. + * Compares two {@code char} values numerically. This is the same functionality as provided in Java 7. * - *

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

+ * @param x the first {@code char} to compare + * @param y the second {@code char} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 */ - public CharUtils() { + public static int compare(final char x, final char y) { + return x - y; } /** - * Converts the character to a Character. - * - *

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

+ * Checks whether the character is ASCII 7 bit. * *
-     *   CharUtils.toCharacterObject(' ')  = ' '
-     *   CharUtils.toCharacterObject('A')  = 'A'
+     *   CharUtils.isAscii('a')  = true
+     *   CharUtils.isAscii('A')  = true
+     *   CharUtils.isAscii('3')  = true
+     *   CharUtils.isAscii('-')  = true
+     *   CharUtils.isAscii('\n') = true
+     *   CharUtils.isAscii('©') = false
      * 
* - * @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 + * @param ch the character to check + * @return true if less than 128 */ - @Deprecated - public static Character toCharacterObject(final char ch) { - return Character.valueOf(ch); + public static boolean isAscii(final char ch) { + return ch < 128; } /** - * 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.

+ * Checks whether the character is ASCII 7 bit alphabetic. * *
-     *   CharUtils.toCharacterObject(null) = null
-     *   CharUtils.toCharacterObject("")   = null
-     *   CharUtils.toCharacterObject("A")  = 'A'
-     *   CharUtils.toCharacterObject("BA") = 'B'
+     *   CharUtils.isAsciiAlpha('a')  = true
+     *   CharUtils.isAsciiAlpha('A')  = true
+     *   CharUtils.isAsciiAlpha('3')  = false
+     *   CharUtils.isAsciiAlpha('-')  = false
+     *   CharUtils.isAsciiAlpha('\n') = false
+     *   CharUtils.isAsciiAlpha('©') = false
      * 
* - * @param str the character to convert - * @return the Character value of the first letter of the String + * @param ch the character to check + * @return true if between 65 and 90 or 97 and 122 inclusive */ - public static Character toCharacterObject(final String str) { - return StringUtils.isEmpty(str) ? null : Character.valueOf(str.charAt(0)); + public static boolean isAsciiAlpha(final char ch) { + return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch); + } + + /** + * 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(final char ch) { + return ch >= 'a' && ch <= 'z'; + } + + /** + * 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(final char ch) { + return isAsciiAlpha(ch) || isAsciiNumeric(ch); + } + + /** + * 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(final char ch) { + return ch >= 'A' && ch <= 'Z'; + } + + /** + * 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(final char ch) { + return ch < 32 || ch == 127; + } + + /** + * 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(final char ch) { + return ch >= '0' && ch <= '9'; + } + + /** + * 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(final char ch) { + return ch >= 32 && ch < 127; } /** @@ -188,6 +303,47 @@ public class CharUtils { return StringUtils.isEmpty(str) ? defaultValue : str.charAt(0); } + /** + * 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(final 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(final String str) { + return StringUtils.isEmpty(str) ? null : Character.valueOf(str.charAt(0)); + } + /** * Converts the character to the Integer it represents, throwing an * exception if the character is not numeric. @@ -354,168 +510,12 @@ public class CharUtils { } /** - * Checks whether the character is ASCII 7 bit. + * {@link CharUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code CharUtils.toString('c');}. * - *
-     *   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 + *

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

*/ - public static boolean isAscii(final 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(final 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(final 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(final char ch) { - return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch); - } - - /** - * 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(final 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(final 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(final 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(final char ch) { - return isAsciiAlpha(ch) || isAsciiNumeric(ch); - } - - /** - * Compares two {@code char} values numerically. This is the same functionality as provided in Java 7. - * - * @param x the first {@code char} to compare - * @param y the second {@code char} to compare - * @return the value {@code 0} if {@code x == y}; - * a value less than {@code 0} if {@code x < y}; and - * a value greater than {@code 0} if {@code x > y} - * @since 3.4 - */ - public static int compare(final char x, final char y) { - return x - y; + public CharUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java b/src/main/java/org/apache/commons/lang3/ClassUtils.java index 1d8396864..6ef65d7f0 100644 --- a/src/main/java/org/apache/commons/lang3/ClassUtils.java +++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java @@ -1408,15 +1408,6 @@ public class ClassUtils { return cls != null && cls.getEnclosingClass() != null; } - /** - * Tests whether a {@link Class} is public. - * @param cls Class to test. - * @return {@code true} if {@code cls} is public. - * @since 3.13.0 - */ - public static boolean isPublic(final Class cls) { - return Modifier.isPublic(cls.getModifiers()); - } /** * Returns whether the given {@code type} is a primitive or primitive wrapper ({@link Boolean}, {@link Byte}, * {@link Character}, {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}). @@ -1432,7 +1423,6 @@ public class ClassUtils { } return type.isPrimitive() || isPrimitiveWrapper(type); } - /** * Returns whether the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, * {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}). @@ -1446,6 +1436,16 @@ public class ClassUtils { return wrapperPrimitiveMap.containsKey(type); } + /** + * Tests whether a {@link Class} is public. + * @param cls Class to test. + * @return {@code true} if {@code cls} is public. + * @since 3.13.0 + */ + public static boolean isPublic(final Class cls) { + return Modifier.isPublic(cls.getModifiers()); + } + /** * Converts the specified array of primitive Class objects to an array of its corresponding wrapper Class objects. * diff --git a/src/main/java/org/apache/commons/lang3/Conversion.java b/src/main/java/org/apache/commons/lang3/Conversion.java index 89fb4966e..51a644fca 100644 --- a/src/main/java/org/apache/commons/lang3/Conversion.java +++ b/src/main/java/org/apache/commons/lang3/Conversion.java @@ -80,192 +80,105 @@ public class Conversion { private static final boolean[] FFFF = {false, false, false, false}; /** - * Converts a hexadecimal digit into an int using the default (Lsb0) bit ordering. + * Converts the first 4 bits of a binary (represented as boolean array) in big endian Msb0 + * bit ordering to a hexadecimal digit. * *

- * '1' is converted to 1 + * (1, 0, 0, 0) is converted as follow: '8' (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0) is converted + * to '4' *

* - * @param hexDigit the hexadecimal digit to convert - * @return an int equals to {@code hexDigit} - * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + * @param src the binary to convert + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} */ - public static int hexDigitToInt(final char hexDigit) { - final int digit = Character.digit(hexDigit, 16); - if (digit < 0) { - throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); - } - return digit; + public static char binaryBeMsb0ToHexDigit(final boolean[] src) { + return binaryBeMsb0ToHexDigit(src, 0); } /** - * Converts a hexadecimal digit into an int using the Msb0 bit ordering. + * Converts a binary (represented as boolean array) in big endian Msb0 bit ordering to a + * hexadecimal digit. * *

- * '1' is converted to 8 + * (1, 0, 0, 0) with srcPos = 0 is converted as follow: '8' (1, 0, 0, 0, 0, 0, 0, 0, + * 0, 0, 0, 1, 0, 1, 0, 0) with srcPos = 2 is converted to '5' *

* - * @param hexDigit the hexadecimal digit to convert - * @return an int equals to {@code hexDigit} - * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + * @param src the binary to convert + * @param srcPos the position of the lsb to start the conversion + * @return a hexadecimal digit representing the selected bits + * @throws IllegalArgumentException if {@code src} is empty + * @throws NullPointerException if {@code src} is {@code null} + * @throws IndexOutOfBoundsException if {@code srcPos} is outside the array. */ - public static int hexDigitMsb0ToInt(final char hexDigit) { - switch (hexDigit) { - case '0': - return 0x0; - case '1': - return 0x8; - case '2': - return 0x4; - case '3': - return 0xC; - case '4': - return 0x2; - case '5': - return 0xA; - case '6': - return 0x6; - case '7': - return 0xE; - case '8': - return 0x1; - case '9': - return 0x9; - case 'a':// fall through - case 'A': - return 0x5; - case 'b':// fall through - case 'B': - return 0xD; - case 'c':// fall through - case 'C': - return 0x3; - case 'd':// fall through - case 'D': - return 0xB; - case 'e':// fall through - case 'E': - return 0x7; - case 'f':// fall through - case 'F': - return 0xF; - default: - throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + public static char binaryBeMsb0ToHexDigit(final boolean[] src, final int srcPos) { + // JDK 9: Objects.checkIndex(int index, int length) + if (Integer.compareUnsigned(srcPos, src.length) >= 0) { + // Throw the correct exception + if (src.length == 0) { + throw new IllegalArgumentException("Cannot convert an empty array."); + } + throw new IndexOutOfBoundsException(srcPos + " is not within array length " + src.length); } + // Little-endian bit 0 position + final int pos = src.length - 1 - srcPos; + if (3 <= pos && src[pos - 3]) { + if (src[pos - 2]) { + if (src[pos - 1]) { + return src[pos] ? 'f' : 'e'; + } + return src[pos] ? 'd' : 'c'; + } + if (src[pos - 1]) { + return src[pos] ? 'b' : 'a'; + } + return src[pos] ? '9' : '8'; + } + if (2 <= pos && src[pos - 2]) { + if (src[pos - 1]) { + return src[pos] ? '7' : '6'; + } + return src[pos] ? '5' : '4'; + } + if (1 <= pos && src[pos - 1]) { + return src[pos] ? '3' : '2'; + } + return src[pos] ? '1' : '0'; } /** - * Converts a hexadecimal digit into binary (represented as boolean array) using the default - * (Lsb0) bit ordering. + * Converts binary (represented as boolean array) into a byte using the default (little + * endian, Lsb0) byte and bit ordering. * - *

- * '1' is converted as follow: (1, 0, 0, 0) - *

- * - * @param hexDigit the hexadecimal digit to convert - * @return a boolean array with the binary representation of {@code hexDigit} - * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination byte + * @param dstPos the position of the lsb, in bits, in the result byte + * @param nBools the number of booleans to convert + * @return a byte containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 8} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} */ - public static boolean[] hexDigitToBinary(final char hexDigit) { - switch (hexDigit) { - case '0': - return FFFF.clone(); - case '1': - return TFFF.clone(); - case '2': - return FTFF.clone(); - case '3': - return TTFF.clone(); - case '4': - return FFTF.clone(); - case '5': - return TFTF.clone(); - case '6': - return FTTF.clone(); - case '7': - return TTTF.clone(); - case '8': - return FFFT.clone(); - case '9': - return TFFT.clone(); - case 'a':// fall through - case 'A': - return FTFT.clone(); - case 'b':// fall through - case 'B': - return TTFT.clone(); - case 'c':// fall through - case 'C': - return FFTT.clone(); - case 'd':// fall through - case 'D': - return TFTT.clone(); - case 'e':// fall through - case 'E': - return FTTT.clone(); - case 'f':// fall through - case 'F': - return TTTT.clone(); - default: - throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + public static byte binaryToByte(final boolean[] src, final int srcPos, final byte dstInit, final int dstPos, + final int nBools) { + if (src.length == 0 && srcPos == 0 || 0 == nBools) { + return dstInit; } - } - - /** - * Converts a hexadecimal digit into binary (represented as boolean array) using the Msb0 - * bit ordering. - * - *

- * '1' is converted as follow: (0, 0, 0, 1) - *

- * - * @param hexDigit the hexadecimal digit to convert - * @return a boolean array with the binary representation of {@code hexDigit} - * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit - */ - public static boolean[] hexDigitMsb0ToBinary(final char hexDigit) { - switch (hexDigit) { - case '0': - return FFFF.clone(); - case '1': - return FFFT.clone(); - case '2': - return FFTF.clone(); - case '3': - return FFTT.clone(); - case '4': - return FTFF.clone(); - case '5': - return FTFT.clone(); - case '6': - return FTTF.clone(); - case '7': - return FTTT.clone(); - case '8': - return TFFF.clone(); - case '9': - return TFFT.clone(); - case 'a':// fall through - case 'A': - return TFTF.clone(); - case 'b':// fall through - case 'B': - return TFTT.clone(); - case 'c':// fall through - case 'C': - return TTFF.clone(); - case 'd':// fall through - case 'D': - return TTFT.clone(); - case 'e':// fall through - case 'E': - return TTTF.clone(); - case 'f':// fall through - case 'F': - return TTTT.clone(); - default: - throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + if (nBools - 1 + dstPos >= 8) { + throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 8"); } + byte out = dstInit; + for (int i = 0; i < nBools; i++) { + final int shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (byte) ((out & ~mask) | bits); + } + return out; } /** @@ -393,72 +306,728 @@ public class Conversion { } /** - * Converts the first 4 bits of a binary (represented as boolean array) in big endian Msb0 - * bit ordering to a hexadecimal digit. - * - *

- * (1, 0, 0, 0) is converted as follow: '8' (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0) is converted - * to '4' - *

+ * Converts binary (represented as boolean array) into an int using the default (little + * endian, Lsb0) byte and bit ordering. * * @param src the binary to convert - * @return a hexadecimal digit representing the selected bits - * @throws IllegalArgumentException if {@code src} is empty + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nBools the number of booleans to convert + * @return an int containing the selected bits * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} */ - public static char binaryBeMsb0ToHexDigit(final boolean[] src) { - return binaryBeMsb0ToHexDigit(src, 0); + public static int binaryToInt(final boolean[] src, final int srcPos, final int dstInit, final int dstPos, + final int nBools) { + if (src.length == 0 && srcPos == 0 || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 32) { + throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 32"); + } + int out = dstInit; + for (int i = 0; i < nBools; i++) { + final int shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (out & ~mask) | bits; + } + return out; } /** - * Converts a binary (represented as boolean array) in big endian Msb0 bit ordering to a - * hexadecimal digit. - * - *

- * (1, 0, 0, 0) with srcPos = 0 is converted as follow: '8' (1, 0, 0, 0, 0, 0, 0, 0, - * 0, 0, 0, 1, 0, 1, 0, 0) with srcPos = 2 is converted to '5' - *

+ * Converts binary (represented as boolean array) into a long using the default (little + * endian, Lsb0) byte and bit ordering. * * @param src the binary to convert - * @param srcPos the position of the lsb to start the conversion - * @return a hexadecimal digit representing the selected bits - * @throws IllegalArgumentException if {@code src} is empty + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nBools the number of booleans to convert + * @return a long containing the selected bits * @throws NullPointerException if {@code src} is {@code null} - * @throws IndexOutOfBoundsException if {@code srcPos} is outside the array. + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} */ - public static char binaryBeMsb0ToHexDigit(final boolean[] src, final int srcPos) { - // JDK 9: Objects.checkIndex(int index, int length) - if (Integer.compareUnsigned(srcPos, src.length) >= 0) { - // Throw the correct exception - if (src.length == 0) { - throw new IllegalArgumentException("Cannot convert an empty array."); - } - throw new IndexOutOfBoundsException(srcPos + " is not within array length " + src.length); + public static long binaryToLong(final boolean[] src, final int srcPos, final long dstInit, final int dstPos, + final int nBools) { + if (src.length == 0 && srcPos == 0 || 0 == nBools) { + return dstInit; } - // Little-endian bit 0 position - final int pos = src.length - 1 - srcPos; - if (3 <= pos && src[pos - 3]) { - if (src[pos - 2]) { - if (src[pos - 1]) { - return src[pos] ? 'f' : 'e'; - } - return src[pos] ? 'd' : 'c'; - } - if (src[pos - 1]) { - return src[pos] ? 'b' : 'a'; - } - return src[pos] ? '9' : '8'; + if (nBools - 1 + dstPos >= 64) { + throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 64"); } - if (2 <= pos && src[pos - 2]) { - if (src[pos - 1]) { - return src[pos] ? '7' : '6'; + long out = dstInit; + for (int i = 0; i < nBools; i++) { + final int shift = i + dstPos; + final long bits = (src[i + srcPos] ? 1L : 0) << shift; + final long mask = 0x1L << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts binary (represented as boolean array) into a short using the default (little + * endian, Lsb0) byte and bit ordering. + * + * @param src the binary to convert + * @param srcPos the position in {@code src}, in boolean unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nBools the number of booleans to convert + * @return a short containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} + */ + public static short binaryToShort(final boolean[] src, final int srcPos, final short dstInit, final int dstPos, + final int nBools) { + if (src.length == 0 && srcPos == 0 || 0 == nBools) { + return dstInit; + } + if (nBools - 1 + dstPos >= 16) { + throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 16"); + } + short out = dstInit; + for (int i = 0; i < nBools; i++) { + final int shift = i + dstPos; + final int bits = (src[i + srcPos] ? 1 : 0) << shift; + final int mask = 0x1 << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + * Converts an array of byte into an int using the default (little endian, Lsb0) byte and bit + * ordering. + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nBytes the number of bytes to convert + * @return an int containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static int byteArrayToInt(final byte[] src, final int srcPos, final int dstInit, final int dstPos, + final int nBytes) { + if (src.length == 0 && srcPos == 0 || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 32) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 32"); + } + int out = dstInit; + for (int i = 0; i < nBytes; i++) { + final int shift = i * 8 + dstPos; + final int bits = (0xff & src[i + srcPos]) << shift; + final int mask = 0xff << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an array of byte into a long using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nBytes the number of bytes to convert + * @return a long containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static long byteArrayToLong(final byte[] src, final int srcPos, final long dstInit, final int dstPos, + final int nBytes) { + if (src.length == 0 && srcPos == 0 || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 64) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 64"); + } + long out = dstInit; + for (int i = 0; i < nBytes; i++) { + final int shift = i * 8 + dstPos; + final long bits = (0xffL & src[i + srcPos]) << shift; + final long mask = 0xffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an array of byte into a short using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the byte array to convert + * @param srcPos the position in {@code src}, in byte unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nBytes the number of bytes to convert + * @return a short containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + */ + public static short byteArrayToShort(final byte[] src, final int srcPos, final short dstInit, final int dstPos, + final int nBytes) { + if (src.length == 0 && srcPos == 0 || 0 == nBytes) { + return dstInit; + } + if ((nBytes - 1) * 8 + dstPos >= 16) { + throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 16"); + } + short out = dstInit; + for (int i = 0; i < nBytes; i++) { + final int shift = i * 8 + dstPos; + final int bits = (0xff & src[i + srcPos]) << shift; + final int mask = 0xff << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + * Converts bytes from an array into a UUID using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the byte array to convert + * @param srcPos the position in {@code src} where to copy the result from + * @return a UUID + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if array does not contain at least 16 bytes beginning + * with {@code srcPos} + */ + public static UUID byteArrayToUuid(final byte[] src, final int srcPos) { + if (src.length - srcPos < 16) { + throw new IllegalArgumentException("Need at least 16 bytes for UUID"); + } + return new UUID(byteArrayToLong(src, srcPos, 0, 0, 8), byteArrayToLong(src, srcPos + 8, 0, 0, 8)); + } + + /** + * Converts a byte into an array of boolean using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the byte to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 8} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] byteToBinary(final byte src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 8) { + throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 8"); + } + for (int i = 0; i < nBools; i++) { + final int shift = i + srcPos; + dst[dstPos + i] = (0x1 & (src >> shift)) != 0; + } + return dst; + } + + /** + * Converts a byte into an array of Char using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the byte to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 8} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String byteToHex(final byte src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 8) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 8"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + final int shift = i * 4 + srcPos; + final int bits = 0xF & (src >> shift); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); } - return src[pos] ? '5' : '4'; } - if (1 <= pos && src[pos - 1]) { - return src[pos] ? '3' : '2'; + return sb.toString(); + } + + /** + * Converts a hexadecimal digit into binary (represented as boolean array) using the Msb0 + * bit ordering. + * + *

+ * '1' is converted as follow: (0, 0, 0, 1) + *

+ * + * @param hexDigit the hexadecimal digit to convert + * @return a boolean array with the binary representation of {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static boolean[] hexDigitMsb0ToBinary(final char hexDigit) { + switch (hexDigit) { + case '0': + return FFFF.clone(); + case '1': + return FFFT.clone(); + case '2': + return FFTF.clone(); + case '3': + return FFTT.clone(); + case '4': + return FTFF.clone(); + case '5': + return FTFT.clone(); + case '6': + return FTTF.clone(); + case '7': + return FTTT.clone(); + case '8': + return TFFF.clone(); + case '9': + return TFFT.clone(); + case 'a':// fall through + case 'A': + return TFTF.clone(); + case 'b':// fall through + case 'B': + return TFTT.clone(); + case 'c':// fall through + case 'C': + return TTFF.clone(); + case 'd':// fall through + case 'D': + return TTFT.clone(); + case 'e':// fall through + case 'E': + return TTTF.clone(); + case 'f':// fall through + case 'F': + return TTTT.clone(); + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); } - return src[pos] ? '1' : '0'; + } + + /** + * Converts a hexadecimal digit into an int using the Msb0 bit ordering. + * + *

+ * '1' is converted to 8 + *

+ * + * @param hexDigit the hexadecimal digit to convert + * @return an int equals to {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static int hexDigitMsb0ToInt(final char hexDigit) { + switch (hexDigit) { + case '0': + return 0x0; + case '1': + return 0x8; + case '2': + return 0x4; + case '3': + return 0xC; + case '4': + return 0x2; + case '5': + return 0xA; + case '6': + return 0x6; + case '7': + return 0xE; + case '8': + return 0x1; + case '9': + return 0x9; + case 'a':// fall through + case 'A': + return 0x5; + case 'b':// fall through + case 'B': + return 0xD; + case 'c':// fall through + case 'C': + return 0x3; + case 'd':// fall through + case 'D': + return 0xB; + case 'e':// fall through + case 'E': + return 0x7; + case 'f':// fall through + case 'F': + return 0xF; + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + } + + /** + * Converts a hexadecimal digit into binary (represented as boolean array) using the default + * (Lsb0) bit ordering. + * + *

+ * '1' is converted as follow: (1, 0, 0, 0) + *

+ * + * @param hexDigit the hexadecimal digit to convert + * @return a boolean array with the binary representation of {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static boolean[] hexDigitToBinary(final char hexDigit) { + switch (hexDigit) { + case '0': + return FFFF.clone(); + case '1': + return TFFF.clone(); + case '2': + return FTFF.clone(); + case '3': + return TTFF.clone(); + case '4': + return FFTF.clone(); + case '5': + return TFTF.clone(); + case '6': + return FTTF.clone(); + case '7': + return TTTF.clone(); + case '8': + return FFFT.clone(); + case '9': + return TFFT.clone(); + case 'a':// fall through + case 'A': + return FTFT.clone(); + case 'b':// fall through + case 'B': + return TTFT.clone(); + case 'c':// fall through + case 'C': + return FFTT.clone(); + case 'd':// fall through + case 'D': + return TFTT.clone(); + case 'e':// fall through + case 'E': + return FTTT.clone(); + case 'f':// fall through + case 'F': + return TTTT.clone(); + default: + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + } + + /** + * Converts a hexadecimal digit into an int using the default (Lsb0) bit ordering. + * + *

+ * '1' is converted to 1 + *

+ * + * @param hexDigit the hexadecimal digit to convert + * @return an int equals to {@code hexDigit} + * @throws IllegalArgumentException if {@code hexDigit} is not a hexadecimal digit + */ + public static int hexDigitToInt(final char hexDigit) { + final int digit = Character.digit(hexDigit, 16); + if (digit < 0) { + throw new IllegalArgumentException("Cannot interpret '" + hexDigit + "' as a hexadecimal digit"); + } + return digit; + } + + /** + * Converts an array of Char into a byte using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the hexadecimal string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination byte + * @param dstPos the position of the lsb, in bits, in the result byte + * @param nHex the number of Chars to convert + * @return a byte containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 8} + */ + public static byte hexToByte(final String src, final int srcPos, final byte dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 8) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 8"); + } + byte out = dstInit; + for (int i = 0; i < nHex; i++) { + final int shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (byte) ((out & ~mask) | bits); + } + return out; + } + + /** + * Converts an array of Char into an int using the default (little endian, Lsb0) byte and bit + * ordering. + * + * @param src the hexadecimal string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nHex the number of Chars to convert + * @return an int containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 32} + */ + public static int hexToInt(final String src, final int srcPos, final int dstInit, final int dstPos, final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 32) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 32"); + } + int out = dstInit; + for (int i = 0; i < nHex; i++) { + final int shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an array of Char into a long using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the hexadecimal string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nHex the number of Chars to convert + * @return a long containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 64} + */ + public static long hexToLong(final String src, final int srcPos, final long dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 64) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 64"); + } + long out = dstInit; + for (int i = 0; i < nHex; i++) { + final int shift = i * 4 + dstPos; + final long bits = (0xfL & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final long mask = 0xfL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an array of Char into a short using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the hexadecimal string to convert + * @param srcPos the position in {@code src}, in Char unit, from where to start the + * conversion + * @param dstInit initial value of the destination short + * @param dstPos the position of the lsb, in bits, in the result short + * @param nHex the number of Chars to convert + * @return a short containing the selected bits + * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 16} + */ + public static short hexToShort(final String src, final int srcPos, final short dstInit, final int dstPos, + final int nHex) { + if (0 == nHex) { + return dstInit; + } + if ((nHex - 1) * 4 + dstPos >= 16) { + throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 16"); + } + short out = dstInit; + for (int i = 0; i < nHex; i++) { + final int shift = i * 4 + dstPos; + final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; + final int mask = 0xf << shift; + out = (short) ((out & ~mask) | bits); + } + return out; + } + + /** + * Converts an array of int into a long using the default (little endian, Lsb0) byte and bit + * ordering. + * + * @param src the int array to convert + * @param srcPos the position in {@code src}, in int unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nInts the number of ints to convert + * @return a long containing the selected bits + * @throws IllegalArgumentException if {@code (nInts-1)*32+dstPos >= 64} + * @throws NullPointerException if {@code src} is {@code null} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nInts > src.length} + */ + public static long intArrayToLong(final int[] src, final int srcPos, final long dstInit, final int dstPos, + final int nInts) { + if (src.length == 0 && srcPos == 0 || 0 == nInts) { + return dstInit; + } + if ((nInts - 1) * 32 + dstPos >= 64) { + throw new IllegalArgumentException("(nInts-1)*32+dstPos is greater or equal to than 64"); + } + long out = dstInit; + for (int i = 0; i < nInts; i++) { + final int shift = i * 32 + dstPos; + final long bits = (0xffffffffL & src[i + srcPos]) << shift; + final long mask = 0xffffffffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an int into an array of boolean using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} + */ + public static boolean[] intToBinary(final int src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; + } + if (nBools - 1 + srcPos >= 32) { + throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 32"); + } + for (int i = 0; i < nBools; i++) { + final int shift = i + srcPos; + dst[dstPos + i] = (0x1 & (src >> shift)) != 0; + } + return dst; + } + + /** + * Converts an int into an array of byte using the default (little endian, Lsb0) byte and bit + * ordering. + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} + */ + public static byte[] intToByteArray(final int src, final int srcPos, final byte[] dst, final int dstPos, + final int nBytes) { + if (0 == nBytes) { + return dst; + } + if ((nBytes - 1) * 8 + srcPos >= 32) { + throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greater or equal to than 32"); + } + for (int i = 0; i < nBytes; i++) { + final int shift = i * 8 + srcPos; + dst[dstPos + i] = (byte) (0xff & (src >> shift)); + } + return dst; + } + + /** + * Converts an int into an array of Char using the default (little endian, Lsb0) byte and bit + * ordering. + * + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 32} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} + */ + public static String intToHex(final int src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { + return dstInit; + } + if ((nHexs - 1) * 4 + srcPos >= 32) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 32"); + } + final StringBuilder sb = new StringBuilder(dstInit); + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + final int shift = i * 4 + srcPos; + final int bits = 0xF & (src >> shift); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } + } + return sb.toString(); } /** @@ -543,456 +1112,130 @@ public class Conversion { } /** - * Converts an array of int into a long using the default (little endian, Lsb0) byte and bit - * ordering. - * - * @param src the int array to convert - * @param srcPos the position in {@code src}, in int unit, from where to start the - * conversion - * @param dstInit initial value of the destination long - * @param dstPos the position of the lsb, in bits, in the result long - * @param nInts the number of ints to convert - * @return a long containing the selected bits - * @throws IllegalArgumentException if {@code (nInts-1)*32+dstPos >= 64} - * @throws NullPointerException if {@code src} is {@code null} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nInts > src.length} - */ - public static long intArrayToLong(final int[] src, final int srcPos, final long dstInit, final int dstPos, - final int nInts) { - if (src.length == 0 && srcPos == 0 || 0 == nInts) { - return dstInit; - } - if ((nInts - 1) * 32 + dstPos >= 64) { - throw new IllegalArgumentException("(nInts-1)*32+dstPos is greater or equal to than 64"); - } - long out = dstInit; - for (int i = 0; i < nInts; i++) { - final int shift = i * 32 + dstPos; - final long bits = (0xffffffffL & src[i + srcPos]) << shift; - final long mask = 0xffffffffL << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts an array of short into a long using the default (little endian, Lsb0) byte and + * Converts an int into an array of short using the default (little endian, Lsb0) byte and * bit ordering. * - * @param src the short array to convert - * @param srcPos the position in {@code src}, in short unit, from where to start the - * conversion - * @param dstInit initial value of the destination long - * @param dstPos the position of the lsb, in bits, in the result long - * @param nShorts the number of shorts to convert - * @return a long containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 64} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + * @param src the int to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nShorts the number of shorts to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+srcPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nShorts > dst.length} */ - public static long shortArrayToLong(final short[] src, final int srcPos, final long dstInit, final int dstPos, + public static short[] intToShortArray(final int src, final int srcPos, final short[] dst, final int dstPos, final int nShorts) { - if (src.length == 0 && srcPos == 0 || 0 == nShorts) { - return dstInit; + if (0 == nShorts) { + return dst; } - if ((nShorts - 1) * 16 + dstPos >= 64) { - throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greater or equal to than 64"); + if ((nShorts - 1) * 16 + srcPos >= 32) { + throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greater or equal to than 32"); } - long out = dstInit; for (int i = 0; i < nShorts; i++) { - final int shift = i * 16 + dstPos; - final long bits = (0xffffL & src[i + srcPos]) << shift; - final long mask = 0xffffL << shift; - out = (out & ~mask) | bits; + final int shift = i * 16 + srcPos; + dst[dstPos + i] = (short) (0xffff & (src >> shift)); } - return out; + return dst; } /** - * Converts an array of short into an int using the default (little endian, Lsb0) byte and + * Converts a long into an array of boolean using the default (little endian, Lsb0) byte and * bit ordering. * - * @param src the short array to convert - * @param srcPos the position in {@code src}, in short unit, from where to start the - * conversion - * @param dstInit initial value of the destination int - * @param dstPos the position of the lsb, in bits, in the result int - * @param nShorts the number of shorts to convert - * @return an int containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to + * the width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} */ - public static int shortArrayToInt(final short[] src, final int srcPos, final int dstInit, final int dstPos, - final int nShorts) { - if (src.length == 0 && srcPos == 0 || 0 == nShorts) { - return dstInit; + public static boolean[] longToBinary(final long src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { + return dst; } - if ((nShorts - 1) * 16 + dstPos >= 32) { - throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greater or equal to than 32"); + if (nBools - 1 + srcPos >= 64) { + throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 64"); } - int out = dstInit; - for (int i = 0; i < nShorts; i++) { - final int shift = i * 16 + dstPos; - final int bits = (0xffff & src[i + srcPos]) << shift; - final int mask = 0xffff << shift; - out = (out & ~mask) | bits; + for (int i = 0; i < nBools; i++) { + final int shift = i + srcPos; + dst[dstPos + i] = (0x1 & (src >> shift)) != 0; } - return out; + return dst; } /** - * Converts an array of byte into a long using the default (little endian, Lsb0) byte and + * Converts a long into an array of byte using the default (little endian, Lsb0) byte and * bit ordering. * - * @param src the byte array to convert - * @param srcPos the position in {@code src}, in byte unit, from where to start the - * conversion - * @param dstInit initial value of the destination long - * @param dstPos the position of the lsb, in bits, in the result long - * @param nBytes the number of bytes to convert - * @return a long containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 64} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dst the destination array + * @param dstPos the position in {@code dst} where to copy the result + * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws NullPointerException if {@code dst} is {@code null} + * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} */ - public static long byteArrayToLong(final byte[] src, final int srcPos, final long dstInit, final int dstPos, + public static byte[] longToByteArray(final long src, final int srcPos, final byte[] dst, final int dstPos, final int nBytes) { - if (src.length == 0 && srcPos == 0 || 0 == nBytes) { - return dstInit; + if (0 == nBytes) { + return dst; } - if ((nBytes - 1) * 8 + dstPos >= 64) { - throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 64"); + if ((nBytes - 1) * 8 + srcPos >= 64) { + throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greater or equal to than 64"); } - long out = dstInit; for (int i = 0; i < nBytes; i++) { - final int shift = i * 8 + dstPos; - final long bits = (0xffL & src[i + srcPos]) << shift; - final long mask = 0xffL << shift; - out = (out & ~mask) | bits; + final int shift = i * 8 + srcPos; + dst[dstPos + i] = (byte) (0xff & (src >> shift)); } - return out; + return dst; } /** - * Converts an array of byte into an int using the default (little endian, Lsb0) byte and bit - * ordering. - * - * @param src the byte array to convert - * @param srcPos the position in {@code src}, in byte unit, from where to start the - * conversion - * @param dstInit initial value of the destination int - * @param dstPos the position of the lsb, in bits, in the result int - * @param nBytes the number of bytes to convert - * @return an int containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} - */ - public static int byteArrayToInt(final byte[] src, final int srcPos, final int dstInit, final int dstPos, - final int nBytes) { - if (src.length == 0 && srcPos == 0 || 0 == nBytes) { - return dstInit; - } - if ((nBytes - 1) * 8 + dstPos >= 32) { - throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 32"); - } - int out = dstInit; - for (int i = 0; i < nBytes; i++) { - final int shift = i * 8 + dstPos; - final int bits = (0xff & src[i + srcPos]) << shift; - final int mask = 0xff << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts an array of byte into a short using the default (little endian, Lsb0) byte and + * Converts a long into an array of Char using the default (little endian, Lsb0) byte and * bit ordering. * - * @param src the byte array to convert - * @param srcPos the position in {@code src}, in byte unit, from where to start the - * conversion - * @param dstInit initial value of the destination short - * @param dstPos the position of the lsb, in bits, in the result short - * @param nBytes the number of bytes to convert - * @return a short containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code (nBytes-1)*8+dstPos >= 16} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBytes > src.length} + * @param src the long to convert + * @param srcPos the position in {@code src}, in bits, from where to start the conversion + * @param dstInit the initial value for the result String + * @param dstPos the position in {@code dst} where to copy the result + * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the + * width of the input (from srcPos to msb) + * @return {@code dst} + * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 64} + * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} */ - public static short byteArrayToShort(final byte[] src, final int srcPos, final short dstInit, final int dstPos, - final int nBytes) { - if (src.length == 0 && srcPos == 0 || 0 == nBytes) { + public static String longToHex(final long src, final int srcPos, final String dstInit, final int dstPos, + final int nHexs) { + if (0 == nHexs) { return dstInit; } - if ((nBytes - 1) * 8 + dstPos >= 16) { - throw new IllegalArgumentException("(nBytes-1)*8+dstPos is greater or equal to than 16"); + if ((nHexs - 1) * 4 + srcPos >= 64) { + throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 64"); } - short out = dstInit; - for (int i = 0; i < nBytes; i++) { - final int shift = i * 8 + dstPos; - final int bits = (0xff & src[i + srcPos]) << shift; - final int mask = 0xff << shift; - out = (short) ((out & ~mask) | bits); + final StringBuilder sb = new StringBuilder(dstInit); + int append = sb.length(); + for (int i = 0; i < nHexs; i++) { + final int shift = i * 4 + srcPos; + final int bits = (int) (0xF & (src >> shift)); + if (dstPos + i == append) { + ++append; + sb.append(intToHexDigit(bits)); + } else { + sb.setCharAt(dstPos + i, intToHexDigit(bits)); + } } - return out; - } - - /** - * Converts an array of Char into a long using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the hexadecimal string to convert - * @param srcPos the position in {@code src}, in Char unit, from where to start the - * conversion - * @param dstInit initial value of the destination long - * @param dstPos the position of the lsb, in bits, in the result long - * @param nHex the number of Chars to convert - * @return a long containing the selected bits - * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 64} - */ - public static long hexToLong(final String src, final int srcPos, final long dstInit, final int dstPos, - final int nHex) { - if (0 == nHex) { - return dstInit; - } - if ((nHex - 1) * 4 + dstPos >= 64) { - throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 64"); - } - long out = dstInit; - for (int i = 0; i < nHex; i++) { - final int shift = i * 4 + dstPos; - final long bits = (0xfL & hexDigitToInt(src.charAt(i + srcPos))) << shift; - final long mask = 0xfL << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts an array of Char into an int using the default (little endian, Lsb0) byte and bit - * ordering. - * - * @param src the hexadecimal string to convert - * @param srcPos the position in {@code src}, in Char unit, from where to start the - * conversion - * @param dstInit initial value of the destination int - * @param dstPos the position of the lsb, in bits, in the result int - * @param nHex the number of Chars to convert - * @return an int containing the selected bits - * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 32} - */ - public static int hexToInt(final String src, final int srcPos, final int dstInit, final int dstPos, final int nHex) { - if (0 == nHex) { - return dstInit; - } - if ((nHex - 1) * 4 + dstPos >= 32) { - throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 32"); - } - int out = dstInit; - for (int i = 0; i < nHex; i++) { - final int shift = i * 4 + dstPos; - final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; - final int mask = 0xf << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts an array of Char into a short using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the hexadecimal string to convert - * @param srcPos the position in {@code src}, in Char unit, from where to start the - * conversion - * @param dstInit initial value of the destination short - * @param dstPos the position of the lsb, in bits, in the result short - * @param nHex the number of Chars to convert - * @return a short containing the selected bits - * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 16} - */ - public static short hexToShort(final String src, final int srcPos, final short dstInit, final int dstPos, - final int nHex) { - if (0 == nHex) { - return dstInit; - } - if ((nHex - 1) * 4 + dstPos >= 16) { - throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 16"); - } - short out = dstInit; - for (int i = 0; i < nHex; i++) { - final int shift = i * 4 + dstPos; - final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; - final int mask = 0xf << shift; - out = (short) ((out & ~mask) | bits); - } - return out; - } - - /** - * Converts an array of Char into a byte using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the hexadecimal string to convert - * @param srcPos the position in {@code src}, in Char unit, from where to start the - * conversion - * @param dstInit initial value of the destination byte - * @param dstPos the position of the lsb, in bits, in the result byte - * @param nHex the number of Chars to convert - * @return a byte containing the selected bits - * @throws IllegalArgumentException if {@code (nHexs-1)*4+dstPos >= 8} - */ - public static byte hexToByte(final String src, final int srcPos, final byte dstInit, final int dstPos, - final int nHex) { - if (0 == nHex) { - return dstInit; - } - if ((nHex - 1) * 4 + dstPos >= 8) { - throw new IllegalArgumentException("(nHexs-1)*4+dstPos is greater or equal to than 8"); - } - byte out = dstInit; - for (int i = 0; i < nHex; i++) { - final int shift = i * 4 + dstPos; - final int bits = (0xf & hexDigitToInt(src.charAt(i + srcPos))) << shift; - final int mask = 0xf << shift; - out = (byte) ((out & ~mask) | bits); - } - return out; - } - - /** - * Converts binary (represented as boolean array) into a long using the default (little - * endian, Lsb0) byte and bit ordering. - * - * @param src the binary to convert - * @param srcPos the position in {@code src}, in boolean unit, from where to start the - * conversion - * @param dstInit initial value of the destination long - * @param dstPos the position of the lsb, in bits, in the result long - * @param nBools the number of booleans to convert - * @return a long containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 64} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} - */ - public static long binaryToLong(final boolean[] src, final int srcPos, final long dstInit, final int dstPos, - final int nBools) { - if (src.length == 0 && srcPos == 0 || 0 == nBools) { - return dstInit; - } - if (nBools - 1 + dstPos >= 64) { - throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 64"); - } - long out = dstInit; - for (int i = 0; i < nBools; i++) { - final int shift = i + dstPos; - final long bits = (src[i + srcPos] ? 1L : 0) << shift; - final long mask = 0x1L << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts binary (represented as boolean array) into an int using the default (little - * endian, Lsb0) byte and bit ordering. - * - * @param src the binary to convert - * @param srcPos the position in {@code src}, in boolean unit, from where to start the - * conversion - * @param dstInit initial value of the destination int - * @param dstPos the position of the lsb, in bits, in the result int - * @param nBools the number of booleans to convert - * @return an int containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} - */ - public static int binaryToInt(final boolean[] src, final int srcPos, final int dstInit, final int dstPos, - final int nBools) { - if (src.length == 0 && srcPos == 0 || 0 == nBools) { - return dstInit; - } - if (nBools - 1 + dstPos >= 32) { - throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 32"); - } - int out = dstInit; - for (int i = 0; i < nBools; i++) { - final int shift = i + dstPos; - final int bits = (src[i + srcPos] ? 1 : 0) << shift; - final int mask = 0x1 << shift; - out = (out & ~mask) | bits; - } - return out; - } - - /** - * Converts binary (represented as boolean array) into a short using the default (little - * endian, Lsb0) byte and bit ordering. - * - * @param src the binary to convert - * @param srcPos the position in {@code src}, in boolean unit, from where to start the - * conversion - * @param dstInit initial value of the destination short - * @param dstPos the position of the lsb, in bits, in the result short - * @param nBools the number of booleans to convert - * @return a short containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 16} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} - */ - public static short binaryToShort(final boolean[] src, final int srcPos, final short dstInit, final int dstPos, - final int nBools) { - if (src.length == 0 && srcPos == 0 || 0 == nBools) { - return dstInit; - } - if (nBools - 1 + dstPos >= 16) { - throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 16"); - } - short out = dstInit; - for (int i = 0; i < nBools; i++) { - final int shift = i + dstPos; - final int bits = (src[i + srcPos] ? 1 : 0) << shift; - final int mask = 0x1 << shift; - out = (short) ((out & ~mask) | bits); - } - return out; - } - - /** - * Converts binary (represented as boolean array) into a byte using the default (little - * endian, Lsb0) byte and bit ordering. - * - * @param src the binary to convert - * @param srcPos the position in {@code src}, in boolean unit, from where to start the - * conversion - * @param dstInit initial value of the destination byte - * @param dstPos the position of the lsb, in bits, in the result byte - * @param nBools the number of booleans to convert - * @return a byte containing the selected bits - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+dstPos >= 8} - * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nBools > src.length} - */ - public static byte binaryToByte(final boolean[] src, final int srcPos, final byte dstInit, final int dstPos, - final int nBools) { - if (src.length == 0 && srcPos == 0 || 0 == nBools) { - return dstInit; - } - if (nBools - 1 + dstPos >= 8) { - throw new IllegalArgumentException("nBools-1+dstPos is greater or equal to than 8"); - } - byte out = dstInit; - for (int i = 0; i < nBools; i++) { - final int shift = i + dstPos; - final int bits = (src[i + srcPos] ? 1 : 0) << shift; - final int mask = 0x1 << shift; - out = (byte) ((out & ~mask) | bits); - } - return out; + return sb.toString(); } /** @@ -1056,91 +1299,98 @@ public class Conversion { } /** - * Converts an int into an array of short using the default (little endian, Lsb0) byte and + * Converts an array of short into an int using the default (little endian, Lsb0) byte and * bit ordering. * - * @param src the int to convert + * @param src the short array to convert + * @param srcPos the position in {@code src}, in short unit, from where to start the + * conversion + * @param dstInit initial value of the destination int + * @param dstPos the position of the lsb, in bits, in the result int + * @param nShorts the number of shorts to convert + * @return an int containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 32} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + */ + public static int shortArrayToInt(final short[] src, final int srcPos, final int dstInit, final int dstPos, + final int nShorts) { + if (src.length == 0 && srcPos == 0 || 0 == nShorts) { + return dstInit; + } + if ((nShorts - 1) * 16 + dstPos >= 32) { + throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greater or equal to than 32"); + } + int out = dstInit; + for (int i = 0; i < nShorts; i++) { + final int shift = i * 16 + dstPos; + final int bits = (0xffff & src[i + srcPos]) << shift; + final int mask = 0xffff << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts an array of short into a long using the default (little endian, Lsb0) byte and + * bit ordering. + * + * @param src the short array to convert + * @param srcPos the position in {@code src}, in short unit, from where to start the + * conversion + * @param dstInit initial value of the destination long + * @param dstPos the position of the lsb, in bits, in the result long + * @param nShorts the number of shorts to convert + * @return a long containing the selected bits + * @throws NullPointerException if {@code src} is {@code null} + * @throws IllegalArgumentException if {@code (nShorts-1)*16+dstPos >= 64} + * @throws ArrayIndexOutOfBoundsException if {@code srcPos + nShorts > src.length} + */ + public static long shortArrayToLong(final short[] src, final int srcPos, final long dstInit, final int dstPos, + final int nShorts) { + if (src.length == 0 && srcPos == 0 || 0 == nShorts) { + return dstInit; + } + if ((nShorts - 1) * 16 + dstPos >= 64) { + throw new IllegalArgumentException("(nShorts-1)*16+dstPos is greater or equal to than 64"); + } + long out = dstInit; + for (int i = 0; i < nShorts; i++) { + final int shift = i * 16 + dstPos; + final long bits = (0xffffL & src[i + srcPos]) << shift; + final long mask = 0xffffL << shift; + out = (out & ~mask) | bits; + } + return out; + } + + /** + * Converts a short into an array of boolean using the default (little endian, Lsb0) byte + * and bit ordering. + * + * @param src the short to convert * @param srcPos the position in {@code src}, in bits, from where to start the conversion * @param dst the destination array * @param dstPos the position in {@code dst} where to copy the result - * @param nShorts the number of shorts to copy to {@code dst}, must be smaller or equal to + * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to * the width of the input (from srcPos to msb) * @return {@code dst} * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code (nShorts-1)*16+srcPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nShorts > dst.length} + * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 16} + * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} */ - public static short[] intToShortArray(final int src, final int srcPos, final short[] dst, final int dstPos, - final int nShorts) { - if (0 == nShorts) { + public static boolean[] shortToBinary(final short src, final int srcPos, final boolean[] dst, final int dstPos, + final int nBools) { + if (0 == nBools) { return dst; } - if ((nShorts - 1) * 16 + srcPos >= 32) { - throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greater or equal to than 32"); + if (nBools - 1 + srcPos >= 16) { + throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 16"); } - for (int i = 0; i < nShorts; i++) { - final int shift = i * 16 + srcPos; - dst[dstPos + i] = (short) (0xffff & (src >> shift)); - } - return dst; - } - - /** - * Converts a long into an array of byte using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the long to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the - * width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 64} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} - */ - public static byte[] longToByteArray(final long src, final int srcPos, final byte[] dst, final int dstPos, - final int nBytes) { - if (0 == nBytes) { - return dst; - } - if ((nBytes - 1) * 8 + srcPos >= 64) { - throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greater or equal to than 64"); - } - for (int i = 0; i < nBytes; i++) { - final int shift = i * 8 + srcPos; - dst[dstPos + i] = (byte) (0xff & (src >> shift)); - } - return dst; - } - - /** - * Converts an int into an array of byte using the default (little endian, Lsb0) byte and bit - * ordering. - * - * @param src the int to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBytes the number of bytes to copy to {@code dst}, must be smaller or equal to the - * width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code (nBytes-1)*8+srcPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length} - */ - public static byte[] intToByteArray(final int src, final int srcPos, final byte[] dst, final int dstPos, - final int nBytes) { - if (0 == nBytes) { - return dst; - } - if ((nBytes - 1) * 8 + srcPos >= 32) { - throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greater or equal to than 32"); - } - for (int i = 0; i < nBytes; i++) { - final int shift = i * 8 + srcPos; - dst[dstPos + i] = (byte) (0xff & (src >> shift)); + assert (nBools - 1) < 16 - srcPos; + for (int i = 0; i < nBools; i++) { + final int shift = i + srcPos; + dst[dstPos + i] = (0x1 & (src >> shift)) != 0; } return dst; } @@ -1175,80 +1425,6 @@ public class Conversion { return dst; } - /** - * Converts a long into an array of Char using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the long to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dstInit the initial value for the result String - * @param dstPos the position in {@code dst} where to copy the result - * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the - * width of the input (from srcPos to msb) - * @return {@code dst} - * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 64} - * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} - */ - public static String longToHex(final long src, final int srcPos, final String dstInit, final int dstPos, - final int nHexs) { - if (0 == nHexs) { - return dstInit; - } - if ((nHexs - 1) * 4 + srcPos >= 64) { - throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 64"); - } - final StringBuilder sb = new StringBuilder(dstInit); - int append = sb.length(); - for (int i = 0; i < nHexs; i++) { - final int shift = i * 4 + srcPos; - final int bits = (int) (0xF & (src >> shift)); - if (dstPos + i == append) { - ++append; - sb.append(intToHexDigit(bits)); - } else { - sb.setCharAt(dstPos + i, intToHexDigit(bits)); - } - } - return sb.toString(); - } - - /** - * Converts an int into an array of Char using the default (little endian, Lsb0) byte and bit - * ordering. - * - * @param src the int to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dstInit the initial value for the result String - * @param dstPos the position in {@code dst} where to copy the result - * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the - * width of the input (from srcPos to msb) - * @return {@code dst} - * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 32} - * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} - */ - public static String intToHex(final int src, final int srcPos, final String dstInit, final int dstPos, - final int nHexs) { - if (0 == nHexs) { - return dstInit; - } - if ((nHexs - 1) * 4 + srcPos >= 32) { - throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 32"); - } - final StringBuilder sb = new StringBuilder(dstInit); - int append = sb.length(); - for (int i = 0; i < nHexs; i++) { - final int shift = i * 4 + srcPos; - final int bits = 0xF & (src >> shift); - if (dstPos + i == append) { - ++append; - sb.append(intToHexDigit(bits)); - } else { - sb.setCharAt(dstPos + i, intToHexDigit(bits)); - } - } - return sb.toString(); - } - /** * Converts a short into an array of Char using the default (little endian, Lsb0) byte and * bit ordering. @@ -1286,164 +1462,6 @@ public class Conversion { return sb.toString(); } - /** - * Converts a byte into an array of Char using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the byte to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dstInit the initial value for the result String - * @param dstPos the position in {@code dst} where to copy the result - * @param nHexs the number of Chars to copy to {@code dst}, must be smaller or equal to the - * width of the input (from srcPos to msb) - * @return {@code dst} - * @throws IllegalArgumentException if {@code (nHexs-1)*4+srcPos >= 8} - * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos} - */ - public static String byteToHex(final byte src, final int srcPos, final String dstInit, final int dstPos, - final int nHexs) { - if (0 == nHexs) { - return dstInit; - } - if ((nHexs - 1) * 4 + srcPos >= 8) { - throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 8"); - } - final StringBuilder sb = new StringBuilder(dstInit); - int append = sb.length(); - for (int i = 0; i < nHexs; i++) { - final int shift = i * 4 + srcPos; - final int bits = 0xF & (src >> shift); - if (dstPos + i == append) { - ++append; - sb.append(intToHexDigit(bits)); - } else { - sb.setCharAt(dstPos + i, intToHexDigit(bits)); - } - } - return sb.toString(); - } - - /** - * Converts a long into an array of boolean using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the long to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to - * the width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 64} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} - */ - public static boolean[] longToBinary(final long src, final int srcPos, final boolean[] dst, final int dstPos, - final int nBools) { - if (0 == nBools) { - return dst; - } - if (nBools - 1 + srcPos >= 64) { - throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 64"); - } - for (int i = 0; i < nBools; i++) { - final int shift = i + srcPos; - dst[dstPos + i] = (0x1 & (src >> shift)) != 0; - } - return dst; - } - - /** - * Converts an int into an array of boolean using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the int to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to - * the width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 32} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} - */ - public static boolean[] intToBinary(final int src, final int srcPos, final boolean[] dst, final int dstPos, - final int nBools) { - if (0 == nBools) { - return dst; - } - if (nBools - 1 + srcPos >= 32) { - throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 32"); - } - for (int i = 0; i < nBools; i++) { - final int shift = i + srcPos; - dst[dstPos + i] = (0x1 & (src >> shift)) != 0; - } - return dst; - } - - /** - * Converts a short into an array of boolean using the default (little endian, Lsb0) byte - * and bit ordering. - * - * @param src the short to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to - * the width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 16} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} - */ - public static boolean[] shortToBinary(final short src, final int srcPos, final boolean[] dst, final int dstPos, - final int nBools) { - if (0 == nBools) { - return dst; - } - if (nBools - 1 + srcPos >= 16) { - throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 16"); - } - assert (nBools - 1) < 16 - srcPos; - for (int i = 0; i < nBools; i++) { - final int shift = i + srcPos; - dst[dstPos + i] = (0x1 & (src >> shift)) != 0; - } - return dst; - } - - /** - * Converts a byte into an array of boolean using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the byte to convert - * @param srcPos the position in {@code src}, in bits, from where to start the conversion - * @param dst the destination array - * @param dstPos the position in {@code dst} where to copy the result - * @param nBools the number of booleans to copy to {@code dst}, must be smaller or equal to - * the width of the input (from srcPos to msb) - * @return {@code dst} - * @throws NullPointerException if {@code dst} is {@code null} - * @throws IllegalArgumentException if {@code nBools-1+srcPos >= 8} - * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBools > dst.length} - */ - public static boolean[] byteToBinary(final byte src, final int srcPos, final boolean[] dst, final int dstPos, - final int nBools) { - if (0 == nBools) { - return dst; - } - if (nBools - 1 + srcPos >= 8) { - throw new IllegalArgumentException("nBools-1+srcPos is greater or equal to than 8"); - } - for (int i = 0; i < nBools; i++) { - final int shift = i + srcPos; - dst[dstPos + i] = (0x1 & (src >> shift)) != 0; - } - return dst; - } - /** * Converts UUID into an array of byte using the default (little endian, Lsb0) byte and bit * ordering. @@ -1471,22 +1489,4 @@ public class Conversion { } return dst; } - - /** - * Converts bytes from an array into a UUID using the default (little endian, Lsb0) byte and - * bit ordering. - * - * @param src the byte array to convert - * @param srcPos the position in {@code src} where to copy the result from - * @return a UUID - * @throws NullPointerException if {@code src} is {@code null} - * @throws IllegalArgumentException if array does not contain at least 16 bytes beginning - * with {@code srcPos} - */ - public static UUID byteArrayToUuid(final byte[] src, final int srcPos) { - if (src.length - srcPos < 16) { - throw new IllegalArgumentException("Need at least 16 bytes for UUID"); - } - return new UUID(byteArrayToLong(src, srcPos, 0, 0, 8), byteArrayToLong(src, srcPos + 8, 0, 0, 8)); - } } diff --git a/src/main/java/org/apache/commons/lang3/JavaVersion.java b/src/main/java/org/apache/commons/lang3/JavaVersion.java index d5deb6c2b..a0b828429 100644 --- a/src/main/java/org/apache/commons/lang3/JavaVersion.java +++ b/src/main/java/org/apache/commons/lang3/JavaVersion.java @@ -176,68 +176,6 @@ public enum JavaVersion { */ JAVA_RECENT(maxVersion(), Float.toString(maxVersion())); - /** - * The float value. - */ - private final float value; - - /** - * The standard name. - */ - private final 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(final JavaVersion requiredVersion) { - return this.value >= requiredVersion.value; - } - - /** - * Whether this version of Java is at most the version of Java passed in. - * - *

For example:
- * {@code myVersion.atMost(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 - * @since 3.9 - */ - public boolean atMost(final 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 versionStr 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 versionStr) { - return get(versionStr); - } - /** * Transforms the given string with a Java version number to the * corresponding constant of this enumeration class. This method is used @@ -312,15 +250,17 @@ public enum JavaVersion { } /** - * The string value is overridden to return the standard name. + * Transforms the given string with a Java version number to the + * corresponding constant of this enumeration class. This method is used + * internally. * - *

For example, {@code "1.5"}.

- * - * @return the name, not null + * @param versionStr the Java version as string + * @return the corresponding enumeration constant or null if the + * version is unknown */ - @Override - public String toString() { - return name; + // helper for static importing + static JavaVersion getJavaVersion(final String versionStr) { + return get(versionStr); } /** @@ -350,4 +290,64 @@ public enum JavaVersion { } return defaultReturnValue; } + + /** + * The float value. + */ + private final float value; + + /** + * The standard name. + */ + private final 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(final JavaVersion requiredVersion) { + return this.value >= requiredVersion.value; + } + + /** + * Whether this version of Java is at most the version of Java passed in. + * + *

For example:
+ * {@code myVersion.atMost(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 + * @since 3.9 + */ + public boolean atMost(final JavaVersion requiredVersion) { + return this.value <= requiredVersion.value; + } + + /** + * The string value is overridden to return the standard name. + * + *

For example, {@code "1.5"}.

+ * + * @return the name, not null + */ + @Override + public String toString() { + return name; + } } diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java index 0e65b8425..93f81685b 100644 --- a/src/main/java/org/apache/commons/lang3/LocaleUtils.java +++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java @@ -39,10 +39,6 @@ import java.util.stream.Collectors; */ public class LocaleUtils { - private static final char UNDERSCORE = '_'; - private static final String UNDETERMINED = "und"; - private static final char DASH = '-'; - // class to avoid synchronization (Init on demand) static class SyncAvoid { /** Unmodifiable list of available locales. */ @@ -56,6 +52,10 @@ public class LocaleUtils { AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list)); } } + private static final char UNDERSCORE = '_'; + private static final String UNDETERMINED = "und"; + + private static final char DASH = '-'; /** Concurrent map of language locales by country. */ private static final ConcurrentMap> cLanguagesByCountry = diff --git a/src/main/java/org/apache/commons/lang3/NotImplementedException.java b/src/main/java/org/apache/commons/lang3/NotImplementedException.java index 783b8a259..7317a4071 100644 --- a/src/main/java/org/apache/commons/lang3/NotImplementedException.java +++ b/src/main/java/org/apache/commons/lang3/NotImplementedException.java @@ -69,11 +69,13 @@ public class NotImplementedException extends UnsupportedOperationException { /** * Constructs a NotImplementedException. * - * @param cause cause of the exception + * @param message description of the exception + * @param code code indicating a resource for more information regarding the lack of implementation * @since 3.2 */ - public NotImplementedException(final Throwable cause) { - this(cause, null); + public NotImplementedException(final String message, final String code) { + super(message); + this.code = code; } /** @@ -91,14 +93,25 @@ public class NotImplementedException extends UnsupportedOperationException { * Constructs a NotImplementedException. * * @param message description of the exception + * @param cause cause of the exception * @param code code indicating a resource for more information regarding the lack of implementation * @since 3.2 */ - public NotImplementedException(final String message, final String code) { - super(message); + public NotImplementedException(final String message, final Throwable cause, final String code) { + super(message, cause); this.code = code; } + /** + * Constructs a NotImplementedException. + * + * @param cause cause of the exception + * @since 3.2 + */ + public NotImplementedException(final Throwable cause) { + this(cause, null); + } + /** * Constructs a NotImplementedException. * @@ -111,19 +124,6 @@ public class NotImplementedException extends UnsupportedOperationException { this.code = code; } - /** - * Constructs a NotImplementedException. - * - * @param message description of the exception - * @param cause cause of the exception - * @param code code indicating a resource for more information regarding the lack of implementation - * @since 3.2 - */ - public NotImplementedException(final String message, final Throwable cause, final String code) { - super(message, cause); - this.code = code; - } - /** * Obtain the not implemented code. This is an unformatted piece of text intended to point to * further information regarding the lack of implementation. It might, for example, be an issue diff --git a/src/main/java/org/apache/commons/lang3/ObjectUtils.java b/src/main/java/org/apache/commons/lang3/ObjectUtils.java index 2e5a6c4db..67d6d2cf9 100644 --- a/src/main/java/org/apache/commons/lang3/ObjectUtils.java +++ b/src/main/java/org/apache/commons/lang3/ObjectUtils.java @@ -1333,6 +1333,30 @@ public class ObjectUtils { return obj == null ? nullStr : obj.toString(); } + /** + * Gets the {@code toString} of an {@link Supplier}'s {@link Supplier#get()} returning + * a specified text if {@code null} input. + * + *
+     * ObjectUtils.toString(() -> obj, () -> expensive())
+     * 
+ *
+     * ObjectUtils.toString(() -> null, () -> expensive())         = result of expensive()
+     * ObjectUtils.toString(() -> null, () -> expensive())         = result of expensive()
+     * ObjectUtils.toString(() -> "", () -> expensive())           = ""
+     * ObjectUtils.toString(() -> "bat", () -> expensive())        = "bat"
+     * ObjectUtils.toString(() -> Boolean.TRUE, () -> expensive()) = "true"
+     * 
+ * + * @param obj the Object to {@code toString}, may be null + * @param supplier the Supplier of String used on {@code null} input, may be null + * @return the passed in Object's toString, or {@code nullStr} if {@code null} input + * @since 3.14.0 + */ + public static String toString(final Supplier obj, final Supplier supplier) { + return obj == null ? Suppliers.get(supplier) : toString(obj.get(), supplier); + } + /** * Gets the {@code toString} of an {@link Object} returning * a specified text if {@code null} input. @@ -1358,30 +1382,6 @@ public class ObjectUtils { return obj == null ? Suppliers.get(supplier) : obj.toString(); } - /** - * Gets the {@code toString} of an {@link Supplier}'s {@link Supplier#get()} returning - * a specified text if {@code null} input. - * - *
-     * ObjectUtils.toString(() -> obj, () -> expensive())
-     * 
- *
-     * ObjectUtils.toString(() -> null, () -> expensive())         = result of expensive()
-     * ObjectUtils.toString(() -> null, () -> expensive())         = result of expensive()
-     * ObjectUtils.toString(() -> "", () -> expensive())           = ""
-     * ObjectUtils.toString(() -> "bat", () -> expensive())        = "bat"
-     * ObjectUtils.toString(() -> Boolean.TRUE, () -> expensive()) = "true"
-     * 
- * - * @param obj the Object to {@code toString}, may be null - * @param supplier the Supplier of String used on {@code null} input, may be null - * @return the passed in Object's toString, or {@code nullStr} if {@code null} input - * @since 3.14.0 - */ - public static String toString(final Supplier obj, final Supplier supplier) { - return obj == null ? Suppliers.get(supplier) : toString(obj.get(), supplier); - } - /** * Calls {@link Object#wait(long, int)} for the given Duration. * diff --git a/src/main/java/org/apache/commons/lang3/SerializationException.java b/src/main/java/org/apache/commons/lang3/SerializationException.java index c68787312..a51ecf6fc 100644 --- a/src/main/java/org/apache/commons/lang3/SerializationException.java +++ b/src/main/java/org/apache/commons/lang3/SerializationException.java @@ -50,17 +50,6 @@ public class SerializationException extends RuntimeException { super(msg); } - /** - * Constructs a new {@link SerializationException} with specified - * nested {@link Throwable}. - * - * @param cause The {@link Exception} or {@link Error} - * that caused this exception to be thrown. - */ - public SerializationException(final Throwable cause) { - super(cause); - } - /** * Constructs a new {@link SerializationException} with specified * detail message and nested {@link Throwable}. @@ -73,4 +62,15 @@ public class SerializationException extends RuntimeException { super(msg, cause); } + /** + * Constructs a new {@link SerializationException} with specified + * nested {@link Throwable}. + * + * @param cause The {@link Exception} or {@link Error} + * that caused this exception to be thrown. + */ + public SerializationException(final Throwable cause) { + super(cause); + } + } diff --git a/src/main/java/org/apache/commons/lang3/Streams.java b/src/main/java/org/apache/commons/lang3/Streams.java index 2a9201d75..84ffef643 100644 --- a/src/main/java/org/apache/commons/lang3/Streams.java +++ b/src/main/java/org/apache/commons/lang3/Streams.java @@ -66,6 +66,55 @@ import org.apache.commons.lang3.Functions.FailablePredicate; @Deprecated public class Streams { + /** + * A Collector type for arrays. + * + * @param The array type. + * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.ArrayCollector}. + */ + @Deprecated + public static class ArrayCollector implements Collector, O[]> { + private static final Set characteristics = Collections.emptySet(); + private final Class elementType; + + /** + * Constructs a new instance for the given element type. + * + * @param elementType The element type. + */ + public ArrayCollector(final Class elementType) { + this.elementType = elementType; + } + + @Override + public BiConsumer, O> accumulator() { + return List::add; + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> { + left.addAll(right); + return left; + }; + } + + @Override + public Function, O[]> finisher() { + return list -> list.toArray(ArrayUtils.newInstance(elementType, list.size())); + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + } + /** * A reduced, and simplified version of a {@link Stream} with * failable method signatures. @@ -86,6 +135,58 @@ public class Streams { this.stream = stream; } + /** + * Returns whether all elements of this stream match the provided predicate. + * May not evaluate the predicate on all elements if not necessary for + * determining the result. If the stream is empty then {@code true} is + * returned and the predicate is not evaluated. + * + *

+ * This is a short-circuiting terminal operation. + *

+ * + *

+ * Note + * This method evaluates the universal quantification of the + * predicate over the elements of the stream (for all x P(x)). If the + * stream is empty, the quantification is said to be vacuously + * satisfied and is always {@code true} (regardless of P(x)). + *

+ * + * @param predicate A non-interfering, stateless predicate to apply to + * elements of this stream + * @return {@code true} If either all elements of the stream match the + * provided predicate or the stream is empty, otherwise {@code false}. + */ + public boolean allMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().allMatch(Functions.asPredicate(predicate)); + } + + /** + * Returns whether any elements of this stream match the provided + * predicate. May not evaluate the predicate on all elements if not + * necessary for determining the result. If the stream is empty then + * {@code false} is returned and the predicate is not evaluated. + * + *

+ * This is a short-circuiting terminal operation. + *

+ * + * Note + * This method evaluates the existential quantification of the + * predicate over the elements of the stream (for some x P(x)). + * + * @param predicate A non-interfering, stateless predicate to apply to + * elements of this stream + * @return {@code true} if any elements of the stream match the provided + * predicate, otherwise {@code false} + */ + public boolean anyMatch(final FailablePredicate predicate) { + assertNotTerminated(); + return stream().anyMatch(Functions.asPredicate(predicate)); + } + /** * Throws IllegalStateException if this stream is already terminated. * @@ -97,58 +198,6 @@ public class Streams { } } - /** - * Marks this stream as terminated. - * - * @throws IllegalStateException if this stream is already terminated. - */ - protected void makeTerminated() { - assertNotTerminated(); - terminated = true; - } - - /** - * Returns a FailableStream consisting of the elements of this stream that match - * the given FailablePredicate. - * - *

- * This is an intermediate operation. - *

- * - * @param predicate a non-interfering, stateless predicate to apply to each - * element to determine if it should be included. - * @return the new stream - */ - public FailableStream filter(final FailablePredicate predicate){ - assertNotTerminated(); - stream = stream.filter(Functions.asPredicate(predicate)); - return this; - } - - /** - * Performs an action for each element of this stream. - * - *

- * This is an intermediate operation. - *

- * - *

- * The behavior of this operation is explicitly nondeterministic. - * For parallel stream pipelines, this operation does not - * guarantee to respect the encounter order of the stream, as doing so - * would sacrifice the benefit of parallelism. For any given element, the - * action may be performed at whatever time and in whatever thread the - * library chooses. If the action accesses shared state, it is - * responsible for providing the required synchronization. - *

- * - * @param action a non-interfering action to perform on the elements - */ - public void forEach(final FailableConsumer action) { - makeTerminated(); - stream().forEach(Functions.asConsumer(action)); - } - /** * Performs a mutable reduction operation on the elements of this stream using a * {@link Collector}. A {@link Collector} @@ -271,6 +320,75 @@ public class Streams { return stream().collect(supplier, accumulator, combiner); } + /** + * Returns a FailableStream consisting of the elements of this stream that match + * the given FailablePredicate. + * + *

+ * This is an intermediate operation. + *

+ * + * @param predicate a non-interfering, stateless predicate to apply to each + * element to determine if it should be included. + * @return the new stream + */ + public FailableStream filter(final FailablePredicate predicate){ + assertNotTerminated(); + stream = stream.filter(Functions.asPredicate(predicate)); + return this; + } + + /** + * Performs an action for each element of this stream. + * + *

+ * This is an intermediate operation. + *

+ * + *

+ * The behavior of this operation is explicitly nondeterministic. + * For parallel stream pipelines, this operation does not + * guarantee to respect the encounter order of the stream, as doing so + * would sacrifice the benefit of parallelism. For any given element, the + * action may be performed at whatever time and in whatever thread the + * library chooses. If the action accesses shared state, it is + * responsible for providing the required synchronization. + *

+ * + * @param action a non-interfering action to perform on the elements + */ + public void forEach(final FailableConsumer action) { + makeTerminated(); + stream().forEach(Functions.asConsumer(action)); + } + + /** + * Marks this stream as terminated. + * + * @throws IllegalStateException if this stream is already terminated. + */ + protected void makeTerminated() { + assertNotTerminated(); + terminated = true; + } + + /** + * Returns a stream consisting of the results of applying the given + * function to the elements of this stream. + * + *

+ * This is an intermediate operation. + *

+ * + * @param The element type of the new stream + * @param mapper A non-interfering, stateless function to apply to each element + * @return the new stream + */ + public FailableStream map(final FailableFunction mapper) { + assertNotTerminated(); + return new FailableStream<>(stream.map(Functions.asFunction(mapper))); + } + /** * Performs a reduction on the elements of this stream, using the provided * identity value and an associative accumulation function, and returns @@ -325,23 +443,6 @@ public class Streams { return stream().reduce(identity, accumulator); } - /** - * Returns a stream consisting of the results of applying the given - * function to the elements of this stream. - * - *

- * This is an intermediate operation. - *

- * - * @param The element type of the new stream - * @param mapper A non-interfering, stateless function to apply to each element - * @return the new stream - */ - public FailableStream map(final FailableFunction mapper) { - assertNotTerminated(); - return new FailableStream<>(stream.map(Functions.asFunction(mapper))); - } - /** * Converts the FailableStream into an equivalent stream. * @return A stream, which will return the same elements, which this FailableStream would return. @@ -349,100 +450,6 @@ public class Streams { public Stream stream() { return stream; } - - /** - * Returns whether all elements of this stream match the provided predicate. - * May not evaluate the predicate on all elements if not necessary for - * determining the result. If the stream is empty then {@code true} is - * returned and the predicate is not evaluated. - * - *

- * This is a short-circuiting terminal operation. - *

- * - *

- * Note - * This method evaluates the universal quantification of the - * predicate over the elements of the stream (for all x P(x)). If the - * stream is empty, the quantification is said to be vacuously - * satisfied and is always {@code true} (regardless of P(x)). - *

- * - * @param predicate A non-interfering, stateless predicate to apply to - * elements of this stream - * @return {@code true} If either all elements of the stream match the - * provided predicate or the stream is empty, otherwise {@code false}. - */ - public boolean allMatch(final FailablePredicate predicate) { - assertNotTerminated(); - return stream().allMatch(Functions.asPredicate(predicate)); - } - - /** - * Returns whether any elements of this stream match the provided - * predicate. May not evaluate the predicate on all elements if not - * necessary for determining the result. If the stream is empty then - * {@code false} is returned and the predicate is not evaluated. - * - *

- * This is a short-circuiting terminal operation. - *

- * - * Note - * This method evaluates the existential quantification of the - * predicate over the elements of the stream (for some x P(x)). - * - * @param predicate A non-interfering, stateless predicate to apply to - * elements of this stream - * @return {@code true} if any elements of the stream match the provided - * predicate, otherwise {@code false} - */ - public boolean anyMatch(final FailablePredicate predicate) { - assertNotTerminated(); - return stream().anyMatch(Functions.asPredicate(predicate)); - } - } - - /** - * Converts the given {@link Stream stream} into a {@link FailableStream}. - * This is basically a simplified, reduced version of the {@link Stream} - * class, with the same underlying element stream, except that failable - * objects, like {@link FailablePredicate}, {@link FailableFunction}, or - * {@link FailableConsumer} may be applied, instead of - * {@link Predicate}, {@link Function}, or {@link Consumer}. The idea is - * to rewrite a code snippet like this: - *
-     *     final List<O> list;
-     *     final Method m;
-     *     final Function<O,String> mapper = (o) -> {
-     *         try {
-     *             return (String) m.invoke(o);
-     *         } catch (Throwable t) {
-     *             throw Functions.rethrow(t);
-     *         }
-     *     };
-     *     final List<String> strList = list.stream()
-     *         .map(mapper).collect(Collectors.toList());
-     *  
- * as follows: - *
-     *     final List<O> list;
-     *     final Method m;
-     *     final List<String> strList = Functions.stream(list.stream())
-     *         .map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
-     *  
- * While the second version may not be quite as - * efficient (because it depends on the creation of additional, - * intermediate objects, of type FailableStream), it is much more - * concise, and readable, and meets the spirit of Lambdas better - * than the first version. - * @param The streams element type. - * @param stream The stream, which is being converted. - * @return The {@link FailableStream}, which has been created by - * converting the stream. - */ - public static FailableStream stream(final Stream stream) { - return new FailableStream<>(stream); } /** @@ -488,52 +495,45 @@ public class Streams { } /** - * A Collector type for arrays. - * - * @param The array type. - * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.ArrayCollector}. + * Converts the given {@link Stream stream} into a {@link FailableStream}. + * This is basically a simplified, reduced version of the {@link Stream} + * class, with the same underlying element stream, except that failable + * objects, like {@link FailablePredicate}, {@link FailableFunction}, or + * {@link FailableConsumer} may be applied, instead of + * {@link Predicate}, {@link Function}, or {@link Consumer}. The idea is + * to rewrite a code snippet like this: + *
+     *     final List<O> list;
+     *     final Method m;
+     *     final Function<O,String> mapper = (o) -> {
+     *         try {
+     *             return (String) m.invoke(o);
+     *         } catch (Throwable t) {
+     *             throw Functions.rethrow(t);
+     *         }
+     *     };
+     *     final List<String> strList = list.stream()
+     *         .map(mapper).collect(Collectors.toList());
+     *  
+ * as follows: + *
+     *     final List<O> list;
+     *     final Method m;
+     *     final List<String> strList = Functions.stream(list.stream())
+     *         .map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+     *  
+ * While the second version may not be quite as + * efficient (because it depends on the creation of additional, + * intermediate objects, of type FailableStream), it is much more + * concise, and readable, and meets the spirit of Lambdas better + * than the first version. + * @param The streams element type. + * @param stream The stream, which is being converted. + * @return The {@link FailableStream}, which has been created by + * converting the stream. */ - @Deprecated - public static class ArrayCollector implements Collector, O[]> { - private static final Set characteristics = Collections.emptySet(); - private final Class elementType; - - /** - * Constructs a new instance for the given element type. - * - * @param elementType The element type. - */ - public ArrayCollector(final Class elementType) { - this.elementType = elementType; - } - - @Override - public Supplier> supplier() { - return ArrayList::new; - } - - @Override - public BiConsumer, O> accumulator() { - return List::add; - } - - @Override - public BinaryOperator> combiner() { - return (left, right) -> { - left.addAll(right); - return left; - }; - } - - @Override - public Function, O[]> finisher() { - return list -> list.toArray(ArrayUtils.newInstance(elementType, list.size())); - } - - @Override - public Set characteristics() { - return characteristics; - } + public static FailableStream stream(final Stream stream) { + return new FailableStream<>(stream); } /** diff --git a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java index 2aee2ca52..96bba1866 100644 --- a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java @@ -45,6 +45,66 @@ public class StringEscapeUtils { /* ESCAPE TRANSLATORS */ + // TODO: Create a parent class - 'SinglePassTranslator' ? + // It would handle the index checking + length returning, + // and could also have an optimization check method. + static class CsvEscaper extends CharSequenceTranslator { + + private static final char CSV_DELIMITER = ','; + private static final char CSV_QUOTE = '"'; + private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); + private static final char[] CSV_SEARCH_CHARS = { CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF }; + + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + + if (index != 0) { + throw new IllegalStateException("CsvEscaper should never reach the [1] index"); + } + + if (StringUtils.containsNone(input.toString(), CSV_SEARCH_CHARS)) { + out.write(input.toString()); + } else { + out.write(CSV_QUOTE); + out.write(StringUtils.replace(input.toString(), CSV_QUOTE_STR, CSV_QUOTE_STR + CSV_QUOTE_STR)); + out.write(CSV_QUOTE); + } + return Character.codePointCount(input, 0, input.length()); + } + } + + static class CsvUnescaper extends CharSequenceTranslator { + + private static final char CSV_DELIMITER = ','; + private static final char CSV_QUOTE = '"'; + private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); + private static final char[] CSV_SEARCH_CHARS = {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF}; + + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + + if (index != 0) { + throw new IllegalStateException("CsvUnescaper should never reach the [1] index"); + } + + if ( input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE ) { + out.write(input.toString()); + return Character.codePointCount(input, 0, input.length()); + } + + // strip quotes + final String quoteless = input.subSequence(1, input.length() - 1).toString(); + + if ( StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS) ) { + // deal with escaped quotes; ie) "" + out.write(StringUtils.replace(quoteless, CSV_QUOTE_STR + CSV_QUOTE_STR, CSV_QUOTE_STR)); + } else { + out.write(input.toString()); + } + return Character.codePointCount(input, 0, input.length()); + } + } + /** * Translator object for escaping Java. * @@ -236,6 +296,8 @@ public class StringEscapeUtils { new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE()) ); + /* UNESCAPE TRANSLATORS */ + /** * Translator object for escaping individual Comma Separated Values. * @@ -247,36 +309,6 @@ public class StringEscapeUtils { */ public static final CharSequenceTranslator ESCAPE_CSV = new CsvEscaper(); - // TODO: Create a parent class - 'SinglePassTranslator' ? - // It would handle the index checking + length returning, - // and could also have an optimization check method. - static class CsvEscaper extends CharSequenceTranslator { - - private static final char CSV_DELIMITER = ','; - private static final char CSV_QUOTE = '"'; - private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); - private static final char[] CSV_SEARCH_CHARS = { CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF }; - - @Override - public int translate(final CharSequence input, final int index, final Writer out) throws IOException { - - if (index != 0) { - throw new IllegalStateException("CsvEscaper should never reach the [1] index"); - } - - if (StringUtils.containsNone(input.toString(), CSV_SEARCH_CHARS)) { - out.write(input.toString()); - } else { - out.write(CSV_QUOTE); - out.write(StringUtils.replace(input.toString(), CSV_QUOTE_STR, CSV_QUOTE_STR + CSV_QUOTE_STR)); - out.write(CSV_QUOTE); - } - return Character.codePointCount(input, 0, input.length()); - } - } - - /* UNESCAPE TRANSLATORS */ - /** * Translator object for unescaping escaped Java. * @@ -383,75 +415,30 @@ public class StringEscapeUtils { */ public static final CharSequenceTranslator UNESCAPE_CSV = new CsvUnescaper(); - static class CsvUnescaper extends CharSequenceTranslator { - - private static final char CSV_DELIMITER = ','; - private static final char CSV_QUOTE = '"'; - private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); - private static final char[] CSV_SEARCH_CHARS = {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF}; - - @Override - public int translate(final CharSequence input, final int index, final Writer out) throws IOException { - - if (index != 0) { - throw new IllegalStateException("CsvUnescaper should never reach the [1] index"); - } - - if ( input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE ) { - out.write(input.toString()); - return Character.codePointCount(input, 0, input.length()); - } - - // strip quotes - final String quoteless = input.subSequence(1, input.length() - 1).toString(); - - if ( StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS) ) { - // deal with escaped quotes; ie) "" - out.write(StringUtils.replace(quoteless, CSV_QUOTE_STR + CSV_QUOTE_STR, CSV_QUOTE_STR)); - } else { - out.write(input.toString()); - } - return Character.codePointCount(input, 0, input.length()); - } - } - /* Helper functions */ /** - * {@link StringEscapeUtils} instances should NOT be constructed in - * standard programming. + * Returns a {@link String} value for a CSV column enclosed in double quotes, + * if required. * - *

Instead, the class should be used as:

- *
StringEscapeUtils.escapeJava("foo");
+ *

If the value contains a comma, newline or double quote, then the + * String value is returned enclosed in double quotes.

* - *

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

+ *

Any double quote characters in the value are escaped with another double quote.

+ * + *

If the value does not contain a comma, newline or double quote, then the + * String value is returned unchanged.

+ * + * see Wikipedia and + * RFC 4180. + * + * @param input the input CSV column String, may be null + * @return the input String, enclosed in double quotes if the value contains a comma, + * newline or double quote, {@code null} if null string input + * @since 2.4 */ - public StringEscapeUtils() { - } - - /** - * Escapes the characters in a {@link String} using Java String rules. - * - *

Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

- * - *

So a tab becomes the characters {@code '\\'} and - * {@code 't'}.

- * - *

The only difference between Java strings and JavaScript strings - * is that in JavaScript, a single quote and forward-slash (/) are escaped.

- * - *

Example:

- *
-     * input string: He didn't say, "Stop!"
-     * output string: He didn't say, \"Stop!\"
-     * 
- * - * @param input String to escape values in, may be null - * @return String with escaped values, {@code null} if null string input - */ - public static final String escapeJava(final String input) { - return ESCAPE_JAVA.translate(input); + public static final String escapeCsv(final String input) { + return ESCAPE_CSV.translate(input); } /** @@ -483,78 +470,16 @@ public class StringEscapeUtils { } /** - * Escapes the characters in a {@link String} using Json String rules. - *

Escapes any values it finds into their Json String form. - * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

+ * Escapes the characters in a {@link String} using HTML entities. + *

Supports only the HTML 3.0 entities.

* - *

So a tab becomes the characters {@code '\\'} and - * {@code 't'}.

- * - *

The only difference between Java strings and Json strings - * is that in Json, forward-slash (/) is escaped.

- * - *

See https://www.ietf.org/rfc/rfc4627.txt for further details.

- * - *

Example:

- *
-     * input string: He didn't say, "Stop!"
-     * output string: He didn't say, \"Stop!\"
-     * 
- * - * @param input String to escape values in, may be null - * @return String with escaped values, {@code null} if null string input - * - * @since 3.2 - */ - public static final String escapeJson(final String input) { - return ESCAPE_JSON.translate(input); - } - - /** - * Unescapes any Java literals found in the {@link String}. - * For example, it will turn a sequence of {@code '\'} and - * {@code 'n'} into a newline character, unless the {@code '\'} - * is preceded by another {@code '\'}. - * - * @param input the {@link String} to unescape, may be null - * @return a new unescaped {@link String}, {@code null} if null string input - */ - public static final String unescapeJava(final String input) { - return UNESCAPE_JAVA.translate(input); - } - - /** - * Unescapes any EcmaScript literals found in the {@link String}. - * - *

For example, it will turn a sequence of {@code '\'} and {@code 'n'} - * into a newline character, unless the {@code '\'} is preceded by another - * {@code '\'}.

- * - * @see #unescapeJava(String) - * @param input the {@link String} to unescape, may be null - * @return A new unescaped {@link String}, {@code null} if null string input + * @param input the {@link String} to escape, may be null + * @return a new escaped {@link String}, {@code null} if null string input * * @since 3.0 */ - public static final String unescapeEcmaScript(final String input) { - return UNESCAPE_ECMASCRIPT.translate(input); - } - - /** - * Unescapes any Json literals found in the {@link String}. - * - *

For example, it will turn a sequence of {@code '\'} and {@code 'n'} - * into a newline character, unless the {@code '\'} is preceded by another - * {@code '\'}.

- * - * @see #unescapeJava(String) - * @param input the {@link String} to unescape, may be null - * @return A new unescaped {@link String}, {@code null} if null string input - * - * @since 3.2 - */ - public static final String unescapeJson(final String input) { - return UNESCAPE_JSON.translate(input); + public static final String escapeHtml3(final String input) { + return ESCAPE_HTML3.translate(input); } /** @@ -589,51 +514,55 @@ public class StringEscapeUtils { } /** - * Escapes the characters in a {@link String} using HTML entities. - *

Supports only the HTML 3.0 entities.

+ * Escapes the characters in a {@link String} using Java String rules. * - * @param input the {@link String} to escape, may be null - * @return a new escaped {@link String}, {@code null} if null string input + *

Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

* - * @since 3.0 + *

So a tab becomes the characters {@code '\\'} and + * {@code 't'}.

+ * + *

The only difference between Java strings and JavaScript strings + * is that in JavaScript, a single quote and forward-slash (/) are escaped.

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * 
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input */ - public static final String escapeHtml3(final String input) { - return ESCAPE_HTML3.translate(input); + public static final String escapeJava(final String input) { + return ESCAPE_JAVA.translate(input); } /** - * Unescapes a string containing entity escapes to a string - * containing the actual Unicode characters corresponding to the - * escapes. Supports HTML 4.0 entities. + * Escapes the characters in a {@link String} using Json String rules. + *

Escapes any values it finds into their Json String form. + * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

* - *

For example, the string {@code "<Français>"} - * will become {@code ""}

+ *

So a tab becomes the characters {@code '\\'} and + * {@code 't'}.

* - *

If an entity is unrecognized, it is left alone, and inserted - * verbatim into the result string. e.g. {@code ">&zzzz;x"} will - * become {@code ">&zzzz;x"}.

+ *

The only difference between Java strings and Json strings + * is that in Json, forward-slash (/) is escaped.

* - * @param input the {@link String} to unescape, may be null - * @return a new unescaped {@link String}, {@code null} if null string input + *

See https://www.ietf.org/rfc/rfc4627.txt for further details.

* - * @since 3.0 + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * 
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * + * @since 3.2 */ - public static final String unescapeHtml4(final String input) { - return UNESCAPE_HTML4.translate(input); - } - - /** - * Unescapes a string containing entity escapes to a string - * containing the actual Unicode characters corresponding to the - * escapes. Supports only HTML 3.0 entities. - * - * @param input the {@link String} to unescape, may be null - * @return a new unescaped {@link String}, {@code null} if null string input - * - * @since 3.0 - */ - public static final String unescapeHtml3(final String input) { - return UNESCAPE_HTML3.translate(input); + public static final String escapeJson(final String input) { + return ESCAPE_JSON.translate(input); } /** @@ -723,52 +652,6 @@ public class StringEscapeUtils { return ESCAPE_XML11.translate(input); } - /** - * Unescapes a string containing XML entity escapes to a string - * containing the actual Unicode characters corresponding to the - * escapes. - * - *

Supports only the five basic XML entities (gt, lt, quot, amp, apos). - * Does not support DTDs or external entities.

- * - *

Note that numerical \\u Unicode codes are unescaped to their respective - * Unicode characters. This may change in future releases.

- * - * @param input the {@link String} to unescape, may be null - * @return a new unescaped {@link String}, {@code null} if null string input - * @see #escapeXml(String) - * @see #escapeXml10(String) - * @see #escapeXml11(String) - */ - public static final String unescapeXml(final String input) { - return UNESCAPE_XML.translate(input); - } - - - /** - * Returns a {@link String} value for a CSV column enclosed in double quotes, - * if required. - * - *

If the value contains a comma, newline or double quote, then the - * String value is returned enclosed in double quotes.

- * - *

Any double quote characters in the value are escaped with another double quote.

- * - *

If the value does not contain a comma, newline or double quote, then the - * String value is returned unchanged.

- * - * see Wikipedia and - * RFC 4180. - * - * @param input the input CSV column String, may be null - * @return the input String, enclosed in double quotes if the value contains a comma, - * newline or double quote, {@code null} if null string input - * @since 2.4 - */ - public static final String escapeCsv(final String input) { - return ESCAPE_CSV.translate(input); - } - /** * Returns a {@link String} value for an unescaped CSV column. * @@ -794,4 +677,121 @@ public class StringEscapeUtils { return UNESCAPE_CSV.translate(input); } + /** + * Unescapes any EcmaScript literals found in the {@link String}. + * + *

For example, it will turn a sequence of {@code '\'} and {@code 'n'} + * into a newline character, unless the {@code '\'} is preceded by another + * {@code '\'}.

+ * + * @see #unescapeJava(String) + * @param input the {@link String} to unescape, may be null + * @return A new unescaped {@link String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeEcmaScript(final String input) { + return UNESCAPE_ECMASCRIPT.translate(input); + } + + /** + * Unescapes a string containing entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes. Supports only HTML 3.0 entities. + * + * @param input the {@link String} to unescape, may be null + * @return a new unescaped {@link String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeHtml3(final String input) { + return UNESCAPE_HTML3.translate(input); + } + + /** + * Unescapes a string containing entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes. Supports HTML 4.0 entities. + * + *

For example, the string {@code "<Français>"} + * will become {@code ""}

+ * + *

If an entity is unrecognized, it is left alone, and inserted + * verbatim into the result string. e.g. {@code ">&zzzz;x"} will + * become {@code ">&zzzz;x"}.

+ * + * @param input the {@link String} to unescape, may be null + * @return a new unescaped {@link String}, {@code null} if null string input + * + * @since 3.0 + */ + public static final String unescapeHtml4(final String input) { + return UNESCAPE_HTML4.translate(input); + } + + /** + * Unescapes any Java literals found in the {@link String}. + * For example, it will turn a sequence of {@code '\'} and + * {@code 'n'} into a newline character, unless the {@code '\'} + * is preceded by another {@code '\'}. + * + * @param input the {@link String} to unescape, may be null + * @return a new unescaped {@link String}, {@code null} if null string input + */ + public static final String unescapeJava(final String input) { + return UNESCAPE_JAVA.translate(input); + } + + /** + * Unescapes any Json literals found in the {@link String}. + * + *

For example, it will turn a sequence of {@code '\'} and {@code 'n'} + * into a newline character, unless the {@code '\'} is preceded by another + * {@code '\'}.

+ * + * @see #unescapeJava(String) + * @param input the {@link String} to unescape, may be null + * @return A new unescaped {@link String}, {@code null} if null string input + * + * @since 3.2 + */ + public static final String unescapeJson(final String input) { + return UNESCAPE_JSON.translate(input); + } + + + /** + * Unescapes a string containing XML entity escapes to a string + * containing the actual Unicode characters corresponding to the + * escapes. + * + *

Supports only the five basic XML entities (gt, lt, quot, amp, apos). + * Does not support DTDs or external entities.

+ * + *

Note that numerical \\u Unicode codes are unescaped to their respective + * Unicode characters. This may change in future releases.

+ * + * @param input the {@link String} to unescape, may be null + * @return a new unescaped {@link String}, {@code null} if null string input + * @see #escapeXml(String) + * @see #escapeXml10(String) + * @see #escapeXml11(String) + */ + public static final String unescapeXml(final String input) { + return UNESCAPE_XML.translate(input); + } + + /** + * {@link StringEscapeUtils} instances should NOT be constructed in + * standard programming. + * + *

Instead, the class should be used as:

+ *
StringEscapeUtils.escapeJava("foo");
+ * + *

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

+ */ + public StringEscapeUtils() { + } + } diff --git a/src/main/java/org/apache/commons/lang3/Validate.java b/src/main/java/org/apache/commons/lang3/Validate.java index c0e37d7b0..5950bd003 100644 --- a/src/main/java/org/apache/commons/lang3/Validate.java +++ b/src/main/java/org/apache/commons/lang3/Validate.java @@ -77,943 +77,84 @@ public class Validate { private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s"; private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s"; - /** - * Constructor. This class should not normally be instantiated. - */ - public Validate() { - } - - /** - * 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(final boolean expression, final String message, final long value) { - if (!expression) { - 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(final boolean expression, final String message, final double value) { - if (!expression) { - 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);
- * - * @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(final boolean expression, final String message, final Object... values) { - if (!expression) { - throw new IllegalArgumentException(getMessage(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(final boolean expression) { - if (!expression) { - throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); - } - } - - /** - * 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...) - * @deprecated Use {@link Objects#requireNonNull(Object)}. - */ - @Deprecated - public static T notNull(final 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 Objects#requireNonNull(Object) - */ - public static T notNull(final T object, final String message, final Object... values) { - return Objects.requireNonNull(object, toSupplier(message, values)); - } - - private static Supplier toSupplier(final String message, final Object... values) { - return () -> getMessage(message, values); - } - - /** - *

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(final T[] array, final String message, final Object... values) { - Objects.requireNonNull(array, toSupplier(message, values)); - if (array.length == 0) { - throw new IllegalArgumentException(getMessage(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(final T[] array) { - return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); - } - - /** - *

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(final T collection, final String message, final Object... values) { - Objects.requireNonNull(collection, toSupplier(message, values)); - if (collection.isEmpty()) { - throw new IllegalArgumentException(getMessage(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(final T collection) { - return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); - } - - /** - * 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(final T map, final String message, final Object... values) { - Objects.requireNonNull(map, toSupplier(message, values)); - if (map.isEmpty()) { - throw new IllegalArgumentException(getMessage(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(final T map) { - return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); - } - - /** - * 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(final T chars, final String message, final Object... values) { - Objects.requireNonNull(chars, toSupplier(message, values)); - if (chars.length() == 0) { - throw new IllegalArgumentException(getMessage(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(final T chars) { - return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); - } - - /** - * 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(final T chars, final String message, final Object... values) { - Objects.requireNonNull(chars, toSupplier(message, values)); - if (StringUtils.isBlank(chars)) { - throw new IllegalArgumentException(getMessage(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(final T chars) { - return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE); - } - - /** - * 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(final T[] array, final String message, final Object... values) { - Objects.requireNonNull(array, "array"); - for (int i = 0; i < array.length; i++) { - if (array[i] == null) { - final Object[] values2 = ArrayUtils.add(values, Integer.valueOf(i)); - throw new IllegalArgumentException(getMessage(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(final T[] array) { - return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); - } - - /** - * 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(final T iterable, final String message, final Object... values) { - Objects.requireNonNull(iterable, "iterable"); - int i = 0; - for (final Iterator it = iterable.iterator(); it.hasNext(); i++) { - if (it.next() == null) { - final Object[] values2 = ArrayUtils.addAll(values, Integer.valueOf(i)); - throw new IllegalArgumentException(getMessage(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(final T iterable) { - return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE); - } - - /** - * 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(final T[] array, final int index, final String message, final Object... values) { - Objects.requireNonNull(array, "array"); - if (index < 0 || index >= array.length) { - throw new IndexOutOfBoundsException(getMessage(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(final T[] array, final int index) { - return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); - } - - /** - * 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(final T collection, final int index, final String message, final Object... values) { - Objects.requireNonNull(collection, "collection"); - if (index < 0 || index >= collection.size()) { - throw new IndexOutOfBoundsException(getMessage(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(final T collection, final int index) { - return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); - } - - /** - * 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(final T chars, final int index, final String message, final Object... values) { - Objects.requireNonNull(chars, "chars"); - if (index < 0 || index >= chars.length()) { - throw new IndexOutOfBoundsException(getMessage(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(final T chars, final int index) { - return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); - } - - /** - * 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(final boolean expression) { - if (!expression) { - 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(final boolean expression, final String message, final Object... values) { - if (!expression) { - throw new IllegalStateException(getMessage(message, values)); - } - } - - /** - * 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(final CharSequence input, final String pattern) { - // TODO when breaking BC, consider returning input - if (!Pattern.matches(pattern, input)) { - 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(final CharSequence input, final String pattern, final String message, final Object... values) { - // TODO when breaking BC, consider returning input - if (!Pattern.matches(pattern, input)) { - throw new IllegalArgumentException(getMessage(message, values)); - } - } - - /** - * Validates that the specified argument is not Not-a-Number (NaN); otherwise - * throwing an exception. - * - *
Validate.notNaN(myDouble);
- * - *

The message of the exception is "The validated value is not a - * number".

- * - * @param value the value to validate - * @throws IllegalArgumentException if the value is not a number - * @see #notNaN(double, String, Object...) - * @since 3.5 - */ - public static void notNaN(final double value) { - notNaN(value, DEFAULT_NOT_NAN_EX_MESSAGE); - } - - /** - * Validates that the specified argument is not Not-a-Number (NaN); otherwise - * throwing an exception with the specified message. - * - *
Validate.notNaN(myDouble, "The value must be a number");
- * - * @param value the value to validate - * @param message the {@link String#format(String, Object...)} exception message if invalid, not null - * @param values the optional values for the formatted exception message - * @throws IllegalArgumentException if the value is not a number - * @see #notNaN(double) - * @since 3.5 - */ - public static void notNaN(final double value, final String message, final Object... values) { - if (Double.isNaN(value)) { - throw new IllegalArgumentException(getMessage(message, values)); - } - } - - /** - * Validates that the specified argument is not infinite or Not-a-Number (NaN); - * otherwise throwing an exception. - * - *
Validate.finite(myDouble);
- * - *

The message of the exception is "The value is invalid: %f".

- * - * @param value the value to validate - * @throws IllegalArgumentException if the value is infinite or Not-a-Number (NaN) - * @see #finite(double, String, Object...) - * @since 3.5 - */ - public static void finite(final double value) { - finite(value, DEFAULT_FINITE_EX_MESSAGE, value); - } - - /** - * Validates that the specified argument is not infinite or Not-a-Number (NaN); - * otherwise throwing an exception with the specified message. - * - *
Validate.finite(myDouble, "The argument must contain a numeric value");
- * - * @param value the value to validate - * @param message the {@link String#format(String, Object...)} exception message if invalid, not null - * @param values the optional values for the formatted exception message - * @throws IllegalArgumentException if the value is infinite or Not-a-Number (NaN) - * @see #finite(double) - * @since 3.5 - */ - public static void finite(final double value, final String message, final Object... values) { - if (Double.isNaN(value) || Double.isInfinite(value)) { - throw new IllegalArgumentException(getMessage(message, values)); - } - } - - /** - * 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 outside the boundaries - * @see #inclusiveBetween(Object, Object, Comparable, String, Object...) - * @since 3.0 - */ - public static void inclusiveBetween(final T start, final T end, final Comparable value) { - // TODO when breaking BC, consider returning 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 outside the boundaries - * @see #inclusiveBetween(Object, Object, Comparable) - * @since 3.0 - */ - public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { - // TODO when breaking BC, consider returning value - if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { - throw new IllegalArgumentException(getMessage(message, values)); - } - } - /** * Validate that the specified primitive value falls between the two - * inclusive values specified; otherwise, throws an exception. + * exclusive values specified; otherwise, throws an exception. * - *
Validate.inclusiveBetween(0, 2, 1);
+ *
Validate.exclusiveBetween(0.1, 2.1, 1.1);
* - * @param start the inclusive start value - * @param end the inclusive end value + * @param start the exclusive start value + * @param end the exclusive end value * @param value the value to validate - * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * @throws IllegalArgumentException if the value falls out of the boundaries * @since 3.3 */ @SuppressWarnings("boxing") - public static void inclusiveBetween(final long start, final long end, final long value) { + public static void exclusiveBetween(final double start, final double end, final double value) { // TODO when breaking BC, consider returning value - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two - * inclusive values specified; otherwise, throws an exception with the + * exclusive values specified; otherwise, throws an exception with the * specified message. * - *
Validate.inclusiveBetween(0, 2, 1, "Not in range");
+ *
Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");
* - * @param start the inclusive start value - * @param end the inclusive end value + * @param start the exclusive start value + * @param end the exclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @since 3.3 */ - public static void inclusiveBetween(final long start, final long end, final long value, final String message) { + public static void exclusiveBetween(final double start, final double end, final double value, final String message) { // TODO when breaking BC, consider returning value - if (value < start || value > end) { + if (value <= start || value >= end) { throw new IllegalArgumentException(message); } } /** * Validate that the specified primitive value falls between the two - * inclusive values specified; otherwise, throws an exception. + * exclusive values specified; otherwise, throws an exception. * - *
Validate.inclusiveBetween(0.1, 2.1, 1.1);
+ *
Validate.exclusiveBetween(0, 2, 1);
* - * @param start the inclusive start value - * @param end the inclusive end value + * @param start the exclusive start value + * @param end the exclusive end value * @param value the value to validate - * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) + * @throws IllegalArgumentException if the value falls out of the boundaries * @since 3.3 */ @SuppressWarnings("boxing") - public static void inclusiveBetween(final double start, final double end, final double value) { + public static void exclusiveBetween(final long start, final long end, final long value) { // TODO when breaking BC, consider returning value - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two - * inclusive values specified; otherwise, throws an exception with the + * exclusive values specified; otherwise, throws an exception with the * specified message. * - *
Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");
+ *
Validate.exclusiveBetween(0, 2, 1, "Not in range");
* - * @param start the inclusive start value - * @param end the inclusive end value + * @param start the exclusive start value + * @param end the exclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @since 3.3 */ - public static void inclusiveBetween(final double start, final double end, final double value, final String message) { + public static void exclusiveBetween(final long start, final long end, final long value, final String message) { // TODO when breaking BC, consider returning value - if (value < start || value > end) { + if (value <= start || value >= end) { throw new IllegalArgumentException(message); } } @@ -1064,128 +205,180 @@ public class Validate { } /** - * Validate that the specified primitive value falls between the two - * exclusive values specified; otherwise, throws an exception. + * Validates that the specified argument is not infinite or Not-a-Number (NaN); + * otherwise throwing an exception. * - *
Validate.exclusiveBetween(0, 2, 1);
+ *
Validate.finite(myDouble);
+ * + *

The message of the exception is "The value is invalid: %f".

+ * + * @param value the value to validate + * @throws IllegalArgumentException if the value is infinite or Not-a-Number (NaN) + * @see #finite(double, String, Object...) + * @since 3.5 + */ + public static void finite(final double value) { + finite(value, DEFAULT_FINITE_EX_MESSAGE, value); + } + + /** + * Validates that the specified argument is not infinite or Not-a-Number (NaN); + * otherwise throwing an exception with the specified message. + * + *
Validate.finite(myDouble, "The argument must contain a numeric value");
* - * @param start the exclusive start value - * @param end the exclusive end value * @param value the value to validate - * @throws IllegalArgumentException if the value falls out of the boundaries + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @throws IllegalArgumentException if the value is infinite or Not-a-Number (NaN) + * @see #finite(double) + * @since 3.5 + */ + public static void finite(final double value, final String message, final Object... values) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + throw new IllegalArgumentException(getMessage(message, values)); + } + } + + /** + * Gets the message using {@link String#format(String, Object...) String.format(message, values)} + * if the values are not empty, otherwise return the message unformatted. + * This method exists to allow validation methods declaring a String message and varargs parameters + * to be used without any message parameters when the message contains special characters, + * e.g. {@code Validate.isTrue(false, "%Failed%")}. + * + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted message + * @return formatted message using {@link String#format(String, Object...) String.format(message, values)} + * if the values are not empty, otherwise return the unformatted message. + */ + private static String getMessage(final String message, final Object... values) { + return ArrayUtils.isEmpty(values) ? message : String.format(message, values); + } + + /** + * Validate that the specified primitive value falls between the two + * inclusive values specified; otherwise, throws an exception. + * + *
Validate.inclusiveBetween(0.1, 2.1, 1.1);
+ * + * @param start the inclusive start value + * @param end the inclusive end value + * @param value the value to validate + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) * @since 3.3 */ @SuppressWarnings("boxing") - public static void exclusiveBetween(final long start, final long end, final long value) { + public static void inclusiveBetween(final double start, final double end, final double value) { // TODO when breaking BC, consider returning value - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two - * exclusive values specified; otherwise, throws an exception with the + * inclusive values specified; otherwise, throws an exception with the * specified message. * - *
Validate.exclusiveBetween(0, 2, 1, "Not in range");
+ *
Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");
* - * @param start the exclusive start value - * @param end the exclusive end value + * @param start the inclusive start value + * @param end the inclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @since 3.3 */ - public static void exclusiveBetween(final long start, final long end, final long value, final String message) { + public static void inclusiveBetween(final double start, final double end, final double value, final String message) { // TODO when breaking BC, consider returning value - if (value <= start || value >= end) { + if (value < start || value > end) { throw new IllegalArgumentException(message); } } /** * Validate that the specified primitive value falls between the two - * exclusive values specified; otherwise, throws an exception. + * inclusive values specified; otherwise, throws an exception. * - *
Validate.exclusiveBetween(0.1, 2.1, 1.1);
+ *
Validate.inclusiveBetween(0, 2, 1);
* - * @param start the exclusive start value - * @param end the exclusive end value + * @param start the inclusive start value + * @param end the inclusive end value * @param value the value to validate - * @throws IllegalArgumentException if the value falls out of the boundaries + * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) * @since 3.3 */ @SuppressWarnings("boxing") - public static void exclusiveBetween(final double start, final double end, final double value) { + public static void inclusiveBetween(final long start, final long end, final long value) { // TODO when breaking BC, consider returning value - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two - * exclusive values specified; otherwise, throws an exception with the + * inclusive values specified; otherwise, throws an exception with the * specified message. * - *
Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");
+ *
Validate.inclusiveBetween(0, 2, 1, "Not in range");
* - * @param start the exclusive start value - * @param end the exclusive end value + * @param start the inclusive start value + * @param end the inclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @since 3.3 */ - public static void exclusiveBetween(final double start, final double end, final double value, final String message) { + public static void inclusiveBetween(final long start, final long end, final long value, final String message) { // TODO when breaking BC, consider returning value - if (value <= start || value >= end) { + if (value < start || value > end) { throw new IllegalArgumentException(message); } } /** - * Validates that the argument is an instance of the specified class, if not throws an exception. + * Validate that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception. * - *

This method is useful when validating according to an arbitrary class

+ *
Validate.inclusiveBetween(0, 2, 1);
* - *
Validate.isInstanceOf(OkClass.class, object);
- * - *

The message of the exception is "Expected type: {type}, actual: {obj_type}"

- * - * @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...) + * @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 outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable, String, Object...) * @since 3.0 */ - public static void isInstanceOf(final Class type, final Object obj) { - // TODO when breaking BC, consider returning obj - if (!type.isInstance(obj)) { - throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), ClassUtils.getName(obj, "null"))); + public static void inclusiveBetween(final T start, final T end, final Comparable value) { + // TODO when breaking BC, consider returning 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 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 that the specified argument object fall between the two + * inclusive values specified; otherwise, throws an exception with the + * specified message. * - *
Validate.isInstanceOf(OkClass.class, object, "Wrong class, object is of class %s",
-     *   object.getClass().getName());
+ *
Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
* - * @param type the class the object must be validated against, not null - * @param obj the object to check, null throws an exception + * @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 argument is not of specified class - * @see #isInstanceOf(Class, Object) + * @throws IllegalArgumentException if the value falls outside the boundaries + * @see #inclusiveBetween(Object, Object, Comparable) * @since 3.0 */ - public static void isInstanceOf(final Class type, final Object obj, final String message, final Object... values) { - // TODO when breaking BC, consider returning obj - if (!type.isInstance(obj)) { + public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + // TODO when breaking BC, consider returning value + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { throw new IllegalArgumentException(getMessage(message, values)); } } @@ -1238,18 +431,825 @@ public class Validate { } /** - * Gets the message using {@link String#format(String, Object...) String.format(message, values)} - * if the values are not empty, otherwise return the message unformatted. - * This method exists to allow validation methods declaring a String message and varargs parameters - * to be used without any message parameters when the message contains special characters, - * e.g. {@code Validate.isTrue(false, "%Failed%")}. + * Validates that the argument is an instance of the specified class, if not 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 message - * @return formatted message using {@link String#format(String, Object...) String.format(message, values)} - * if the values are not empty, otherwise return the unformatted message. + *

This method is useful when validating according to an arbitrary class

+ * + *
Validate.isInstanceOf(OkClass.class, object);
+ * + *

The message of the exception is "Expected type: {type}, actual: {obj_type}"

+ * + * @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 */ - private static String getMessage(final String message, final Object... values) { - return ArrayUtils.isEmpty(values) ? message : String.format(message, values); + public static void isInstanceOf(final Class type, final Object obj) { + // TODO when breaking BC, consider returning obj + if (!type.isInstance(obj)) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), ClassUtils.getName(obj, "null"))); + } + } + + /** + * 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.class, 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(final Class type, final Object obj, final String message, final Object... values) { + // TODO when breaking BC, consider returning obj + if (!type.isInstance(obj)) { + throw new IllegalArgumentException(getMessage(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(final boolean expression) { + if (!expression) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + /** + * 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(final boolean expression, final String message, final double value) { + if (!expression) { + 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 > 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(final boolean expression, final String message, final long value) { + if (!expression) { + 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(i >= min && i <= max, "The value must be between %d and %d", min, max);
+ * + * @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(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalArgumentException(getMessage(message, values)); + } + } + + /** + * 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(final CharSequence input, final String pattern) { + // TODO when breaking BC, consider returning input + if (!Pattern.matches(pattern, input)) { + 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(final CharSequence input, final String pattern, final String message, final Object... values) { + // TODO when breaking BC, consider returning input + if (!Pattern.matches(pattern, input)) { + throw new IllegalArgumentException(getMessage(message, values)); + } + } + + /** + * 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(final T iterable) { + return noNullElements(iterable, DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE); + } + + /** + * 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(final T iterable, final String message, final Object... values) { + Objects.requireNonNull(iterable, "iterable"); + int i = 0; + for (final Iterator it = iterable.iterator(); it.hasNext(); i++) { + if (it.next() == null) { + final Object[] values2 = ArrayUtils.addAll(values, Integer.valueOf(i)); + throw new IllegalArgumentException(getMessage(message, values2)); + } + } + return iterable; + } + + /** + * 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(final T[] array) { + return noNullElements(array, DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); + } + + /** + * 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(final T[] array, final String message, final Object... values) { + Objects.requireNonNull(array, "array"); + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + final Object[] values2 = ArrayUtils.add(values, Integer.valueOf(i)); + throw new IllegalArgumentException(getMessage(message, values2)); + } + } + return array; + } + + /** + *

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(final T chars) { + return notBlank(chars, DEFAULT_NOT_BLANK_EX_MESSAGE); + } + + /** + * 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(final T chars, final String message, final Object... values) { + Objects.requireNonNull(chars, toSupplier(message, values)); + if (StringUtils.isBlank(chars)) { + throw new IllegalArgumentException(getMessage(message, values)); + } + return chars; + } + + /** + *

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(final T collection) { + return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + + /** + *

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(final T map) { + return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + + /** + *

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(final T chars) { + return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + + /** + *

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(final T collection, final String message, final Object... values) { + Objects.requireNonNull(collection, toSupplier(message, values)); + if (collection.isEmpty()) { + throw new IllegalArgumentException(getMessage(message, values)); + } + return collection; + } + + /** + * 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(final T map, final String message, final Object... values) { + Objects.requireNonNull(map, toSupplier(message, values)); + if (map.isEmpty()) { + throw new IllegalArgumentException(getMessage(message, values)); + } + return map; + } + + /** + * 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(final T chars, final String message, final Object... values) { + Objects.requireNonNull(chars, toSupplier(message, values)); + if (chars.length() == 0) { + throw new IllegalArgumentException(getMessage(message, values)); + } + return chars; + } + + /** + *

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(final T[] array) { + return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + + /** + *

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(final T[] array, final String message, final Object... values) { + Objects.requireNonNull(array, toSupplier(message, values)); + if (array.length == 0) { + throw new IllegalArgumentException(getMessage(message, values)); + } + return array; + } + + /** + * Validates that the specified argument is not Not-a-Number (NaN); otherwise + * throwing an exception. + * + *
Validate.notNaN(myDouble);
+ * + *

The message of the exception is "The validated value is not a + * number".

+ * + * @param value the value to validate + * @throws IllegalArgumentException if the value is not a number + * @see #notNaN(double, String, Object...) + * @since 3.5 + */ + public static void notNaN(final double value) { + notNaN(value, DEFAULT_NOT_NAN_EX_MESSAGE); + } + + /** + * Validates that the specified argument is not Not-a-Number (NaN); otherwise + * throwing an exception with the specified message. + * + *
Validate.notNaN(myDouble, "The value must be a number");
+ * + * @param value the value to validate + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @throws IllegalArgumentException if the value is not a number + * @see #notNaN(double) + * @since 3.5 + */ + public static void notNaN(final double value, final String message, final Object... values) { + if (Double.isNaN(value)) { + throw new IllegalArgumentException(getMessage(message, values)); + } + } + + /** + * 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...) + * @deprecated Use {@link Objects#requireNonNull(Object)}. + */ + @Deprecated + public static T notNull(final 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 Objects#requireNonNull(Object) + */ + public static T notNull(final T object, final String message, final Object... values) { + return Objects.requireNonNull(object, toSupplier(message, values)); + } + + private static Supplier toSupplier(final String message, final Object... values) { + return () -> getMessage(message, values); + } + + /** + * 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(final T collection, final int index) { + return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); + } + + /** + * 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(final T chars, final int index) { + return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); + } + + /** + * 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(final T collection, final int index, final String message, final Object... values) { + Objects.requireNonNull(collection, "collection"); + if (index < 0 || index >= collection.size()) { + throw new IndexOutOfBoundsException(getMessage(message, values)); + } + return collection; + } + + /** + * 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(final T chars, final int index, final String message, final Object... values) { + Objects.requireNonNull(chars, "chars"); + if (index < 0 || index >= chars.length()) { + throw new IndexOutOfBoundsException(getMessage(message, values)); + } + return chars; + } + + /** + * 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(final T[] array, final int index) { + return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); + } + + /** + * 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(final T[] array, final int index, final String message, final Object... values) { + Objects.requireNonNull(array, "array"); + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException(getMessage(message, values)); + } + return array; + } + + /** + * 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(final boolean expression) { + if (!expression) { + 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(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalStateException(getMessage(message, values)); + } + } + + /** + * Constructor. This class should not normally be instantiated. + */ + public Validate() { } } diff --git a/src/main/java/org/apache/commons/lang3/builder/Diff.java b/src/main/java/org/apache/commons/lang3/builder/Diff.java index 763cd9ae0..334b1c051 100644 --- a/src/main/java/org/apache/commons/lang3/builder/Diff.java +++ b/src/main/java/org/apache/commons/lang3/builder/Diff.java @@ -60,6 +60,15 @@ public abstract class Diff extends Pair { this.fieldName = fieldName; } + /** + * Gets the name of the field. + * + * @return the field name + */ + public final String getFieldName() { + return fieldName; + } + /** * Gets the type of the field. * @@ -70,12 +79,15 @@ public abstract class Diff extends Pair { } /** - * Gets the name of the field. + * Throws {@link UnsupportedOperationException}. * - * @return the field name + * @param value + * ignored + * @return nothing */ - public final String getFieldName() { - return fieldName; + @Override + public final T setValue(final T value) { + throw new UnsupportedOperationException("Cannot alter Diff object."); } /** @@ -92,16 +104,4 @@ public abstract class Diff extends Pair { public final String toString() { return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight()); } - - /** - * Throws {@link UnsupportedOperationException}. - * - * @param value - * ignored - * @return nothing - */ - @Override - public final T setValue(final T value) { - throw new UnsupportedOperationException("Cannot alter Diff object."); - } } diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java index 4d481c565..1bacfd846 100644 --- a/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java @@ -76,6 +76,36 @@ public class DiffBuilder implements Builder> { private final T right; private final ToStringStyle style; + /** + * Constructs a builder for the specified objects with the specified style. + * + *

+ * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will + * not evaluate any calls to {@code append(...)} and will return an empty + * {@link DiffResult} when {@link #build()} is executed. + *

+ * + *

+ * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} + * with the testTriviallyEqual flag enabled. + *

+ * + * @param lhs + * {@code this} object + * @param rhs + * the object to diff against + * @param style + * the style will use when outputting the objects, {@code null} + * uses the default + * @throws NullPointerException + * if {@code lhs} or {@code rhs} is {@code null} + */ + public DiffBuilder(final T lhs, final T rhs, + final ToStringStyle style) { + + this(lhs, rhs, style, true); + } + /** * Constructs a builder for the specified objects with the specified style. * @@ -117,36 +147,6 @@ public class DiffBuilder implements Builder> { this.objectsTriviallyEqual = testTriviallyEqual && Objects.equals(lhs, rhs); } - /** - * Constructs a builder for the specified objects with the specified style. - * - *

- * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will - * not evaluate any calls to {@code append(...)} and will return an empty - * {@link DiffResult} when {@link #build()} is executed. - *

- * - *

- * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} - * with the testTriviallyEqual flag enabled. - *

- * - * @param lhs - * {@code this} object - * @param rhs - * the object to diff against - * @param style - * the style will use when outputting the objects, {@code null} - * uses the default - * @throws NullPointerException - * if {@code lhs} or {@code rhs} is {@code null} - */ - public DiffBuilder(final T lhs, final T rhs, - final ToStringStyle style) { - - this(lhs, rhs, style, true); - } - /** * Test if two {@code boolean}s are equal. * @@ -373,6 +373,49 @@ public class DiffBuilder implements Builder> { return this; } + /** + * Append diffs from another {@link DiffResult}. + * + *

+ * This method is useful if you want to compare properties which are + * themselves Diffable and would like to know which specific part of + * it is different. + *

+ * + *
+     * public class Person implements Diffable<Person> {
+     *   String name;
+     *   Address address; // implements Diffable<Address>
+     *
+     *   ...
+     *
+     *   public DiffResult diff(Person obj) {
+     *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+     *       .append("name", this.name, obj.name)
+     *       .append("address", this.address.diff(obj.address))
+     *       .build();
+     *   }
+     * }
+     * 
+ * + * @param fieldName + * the field name + * @param diffResult + * the {@link DiffResult} to append + * @return this + * @throws NullPointerException if field name is {@code null} or diffResult is {@code null} + * @since 3.5 + */ + public DiffBuilder append(final String fieldName, final DiffResult diffResult) { + validateFieldNameNotNull(fieldName); + Objects.requireNonNull(diffResult, "diffResult"); + if (objectsTriviallyEqual) { + return this; + } + diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight())); + return this; + } + /** * Test if two {@code double}s are equal. * @@ -677,82 +720,6 @@ public class DiffBuilder implements Builder> { return this; } - /** - * Test if two {@code short}s are equal. - * - * @param fieldName - * the field name - * @param lhs - * the left-hand {@code short} - * @param rhs - * the right-hand {@code short} - * @return this - * @throws NullPointerException - * if field name is {@code null} - */ - public DiffBuilder append(final String fieldName, final short lhs, - final short rhs) { - validateFieldNameNotNull(fieldName); - - if (objectsTriviallyEqual) { - return this; - } - if (lhs != rhs) { - diffs.add(new Diff(fieldName) { - private static final long serialVersionUID = 1L; - - @Override - public Short getLeft() { - return Short.valueOf(lhs); - } - - @Override - public Short getRight() { - return Short.valueOf(rhs); - } - }); - } - return this; - } - - /** - * Test if two {@code short[]}s are equal. - * - * @param fieldName - * the field name - * @param lhs - * the left-hand {@code short[]} - * @param rhs - * the right-hand {@code short[]} - * @return this - * @throws NullPointerException - * if field name is {@code null} - */ - public DiffBuilder append(final String fieldName, final short[] lhs, - final short[] rhs) { - validateFieldNameNotNull(fieldName); - - if (objectsTriviallyEqual) { - return this; - } - if (!Arrays.equals(lhs, rhs)) { - diffs.add(new Diff(fieldName) { - private static final long serialVersionUID = 1L; - - @Override - public Short[] getLeft() { - return ArrayUtils.toObject(lhs); - } - - @Override - public Short[] getRight() { - return ArrayUtils.toObject(rhs); - } - }); - } - return this; - } - /** * Test if two {@link Objects}s are equal. * @@ -875,45 +842,78 @@ public class DiffBuilder implements Builder> { } /** - * Append diffs from another {@link DiffResult}. - * - *

- * This method is useful if you want to compare properties which are - * themselves Diffable and would like to know which specific part of - * it is different. - *

- * - *
-     * public class Person implements Diffable<Person> {
-     *   String name;
-     *   Address address; // implements Diffable<Address>
-     *
-     *   ...
-     *
-     *   public DiffResult diff(Person obj) {
-     *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
-     *       .append("name", this.name, obj.name)
-     *       .append("address", this.address.diff(obj.address))
-     *       .build();
-     *   }
-     * }
-     * 
+ * Test if two {@code short}s are equal. * * @param fieldName * the field name - * @param diffResult - * the {@link DiffResult} to append + * @param lhs + * the left-hand {@code short} + * @param rhs + * the right-hand {@code short} * @return this - * @throws NullPointerException if field name is {@code null} or diffResult is {@code null} - * @since 3.5 + * @throws NullPointerException + * if field name is {@code null} */ - public DiffBuilder append(final String fieldName, final DiffResult diffResult) { + public DiffBuilder append(final String fieldName, final short lhs, + final short rhs) { validateFieldNameNotNull(fieldName); - Objects.requireNonNull(diffResult, "diffResult"); + if (objectsTriviallyEqual) { return this; } - diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight())); + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short getLeft() { + return Short.valueOf(lhs); + } + + @Override + public Short getRight() { + return Short.valueOf(rhs); + } + }); + } + return this; + } + + /** + * Test if two {@code short[]}s are equal. + * + * @param fieldName + * the field name + * @param lhs + * the left-hand {@code short[]} + * @param rhs + * the right-hand {@code short[]} + * @return this + * @throws NullPointerException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short[] lhs, + final short[] rhs) { + validateFieldNameNotNull(fieldName); + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Short[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } return this; } diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffResult.java b/src/main/java/org/apache/commons/lang3/builder/DiffResult.java index a5c2d1053..c4c920d9e 100644 --- a/src/main/java/org/apache/commons/lang3/builder/DiffResult.java +++ b/src/main/java/org/apache/commons/lang3/builder/DiffResult.java @@ -83,6 +83,16 @@ public class DiffResult implements Iterable> { } } + /** + * Returns an unmodifiable list of {@link Diff}s. The list may be empty if + * there were no differences between the objects. + * + * @return an unmodifiable list of {@link Diff}s + */ + public List> getDiffs() { + return Collections.unmodifiableList(diffList); + } + /** * Returns the object the right object has been compared to. * @@ -93,6 +103,15 @@ public class DiffResult implements Iterable> { return this.lhs; } + /** + * Returns the number of differences between the two objects. + * + * @return the number of differences + */ + public int getNumberOfDiffs() { + return diffList.size(); + } + /** * Returns the object the left object has been compared to. * @@ -103,25 +122,6 @@ public class DiffResult implements Iterable> { return this.rhs; } - /** - * Returns an unmodifiable list of {@link Diff}s. The list may be empty if - * there were no differences between the objects. - * - * @return an unmodifiable list of {@link Diff}s - */ - public List> getDiffs() { - return Collections.unmodifiableList(diffList); - } - - /** - * Returns the number of differences between the two objects. - * - * @return the number of differences - */ - public int getNumberOfDiffs() { - return diffList.size(); - } - /** * Returns the style used by the {@link #toString()} method. * @@ -131,6 +131,16 @@ public class DiffResult implements Iterable> { return style; } + /** + * Returns an iterator over the {@link Diff} objects contained in this list. + * + * @return the iterator + */ + @Override + public Iterator> iterator() { + return diffList.iterator(); + } + /** * Builds a {@link String} description of the differences contained within * this {@link DiffResult}. A {@link ToStringBuilder} is used for each object @@ -189,14 +199,4 @@ public class DiffResult implements Iterable> { return String.format("%s %s %s", lhsBuilder.build(), DIFFERS_STRING, rhsBuilder.build()); } - - /** - * Returns an iterator over the {@link Diff} objects contained in this list. - * - * @return the iterator - */ - @Override - public Iterator> iterator() { - return diffList.iterator(); - } } diff --git a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java index 5b7d393e7..e5c4d33e4 100644 --- a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java @@ -112,17 +112,6 @@ public class EqualsBuilder implements Builder { * 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. * @@ -137,6 +126,17 @@ public class EqualsBuilder implements Builder { return Pair.of(left, right); } + /** + * 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(); + } + /** * Returns {@code true} if the registry contains the given object pair. * Used by the reflection methods to avoid infinite loops. @@ -157,185 +157,6 @@ public class EqualsBuilder implements Builder { && (registry.contains(pair) || registry.contains(swappedPair)); } - /** - * Registers the given object pair. - * Used by the reflection methods to avoid infinite loops. - * - * @param lhs {@code this} object to register - * @param rhs the other object to register - */ - private static void register(final Object lhs, final Object rhs) { - Set> registry = getRegistry(); - if (registry == null) { - registry = new HashSet<>(); - REGISTRY.set(registry); - } - final Pair pair = getRegisterPair(lhs, rhs); - registry.add(pair); - } - - /** - * Unregisters the given object pair. - * - *

- * Used by the reflection methods to avoid infinite loops. - * - * @param lhs {@code this} object to unregister - * @param rhs the other object to unregister - * @since 3.0 - */ - private static void unregister(final Object lhs, final Object rhs) { - final Set> registry = getRegistry(); - if (registry != null) { - registry.remove(getRegisterPair(lhs, rhs)); - if (registry.isEmpty()) { - REGISTRY.remove(); - } - } - } - - /** - * If the fields tested are equals. - * The default value is {@code true}. - */ - private boolean isEquals = true; - - private boolean testTransients; - private boolean testRecursive; - private List> bypassReflectionClasses; - private Class reflectUpToClass; - private String[] excludeFields; - - /** - * Constructor for EqualsBuilder. - * - *

Starts off assuming that equals is {@code true}.

- * @see Object#equals(Object) - */ - public EqualsBuilder() { - // set up default classes to bypass reflection for - bypassReflectionClasses = new ArrayList<>(1); - bypassReflectionClasses.add(String.class); //hashCode field being lazy but not transient - } - - /** - * Sets whether to include transient fields when reflectively comparing objects. - * @param testTransients whether to test transient fields - * @return this - * @since 3.6 - */ - public EqualsBuilder setTestTransients(final boolean testTransients) { - this.testTransients = testTransients; - return this; - } - - /** - * Sets whether to test fields recursively, instead of using their equals method, when reflectively comparing objects. - * String objects, which cache a hash value, are automatically excluded from recursive testing. - * You may specify other exceptions by calling {@link #setBypassReflectionClasses(List)}. - * @param testRecursive whether to do a recursive test - * @return this - * @see #setBypassReflectionClasses(List) - * @since 3.6 - */ - public EqualsBuilder setTestRecursive(final boolean testRecursive) { - this.testRecursive = testRecursive; - return this; - } - - /** - * Sets {@link Class}es whose instances should be compared by calling their {@code equals} - * although being in recursive mode. So the fields of theses classes will not be compared recursively by reflection. - * - *

Here you should name classes having non-transient fields which are cache fields being set lazily.
- * Prominent example being {@link String} class with its hash code cache field. Due to the importance - * of the {@link String} class, it is included in the default bypasses classes. Usually, if you use - * your own set of classes here, remember to include {@link String} class, too.

- * @param bypassReflectionClasses classes to bypass reflection test - * @return this - * @see #setTestRecursive(boolean) - * @since 3.8 - */ - public EqualsBuilder setBypassReflectionClasses(final List> bypassReflectionClasses) { - this.bypassReflectionClasses = bypassReflectionClasses; - return this; - } - - /** - * Sets the superclass to reflect up to at reflective tests. - * @param reflectUpToClass the super class to reflect up to - * @return this - * @since 3.6 - */ - public EqualsBuilder setReflectUpToClass(final Class reflectUpToClass) { - this.reflectUpToClass = reflectUpToClass; - return this; - } - - /** - * Sets field names to be excluded by reflection tests. - * @param excludeFields the fields to exclude - * @return this - * @since 3.6 - */ - public EqualsBuilder setExcludeFields(final String... excludeFields) { - this.excludeFields = excludeFields; - return this; - } - - - /** - * This method uses reflection to determine if the two {@link Object}s - * are equal. - * - *

It uses {@code 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. Non-primitive fields are compared using - * {@code equals()}.

- * - *

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 {@code this} object - * @param rhs the other object - * @param excludeFields Collection of String field names to exclude from testing - * @return {@code true} if the two Objects have tested equals. - * - * @see EqualsExclude - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { - return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); - } - - /** - * This method uses reflection to determine if the two {@link Object}s - * are equal. - * - *

It uses {@code 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. Non-primitive fields are compared using - * {@code equals()}.

- * - *

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 {@code this} object - * @param rhs the other object - * @param excludeFields array of field names to exclude from testing - * @return {@code true} if the two Objects have tested equals. - * - * @see EqualsExclude - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { - return reflectionEquals(lhs, rhs, false, null, excludeFields); - } - /** * This method uses reflection to determine if the two {@link Object}s * are equal. @@ -363,40 +184,6 @@ public class EqualsBuilder implements Builder { return reflectionEquals(lhs, rhs, testTransients, null); } - /** - * This method uses reflection to determine if the two {@link Object}s - * are equal. - * - *

It uses {@code 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. Non-primitive fields are compared using - * {@code equals()}.

- * - *

If the testTransients parameter is set to {@code true}, transient - * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the {@link 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 {@code 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 {@code null} - * @param excludeFields array of field names to exclude from testing - * @return {@code true} if the two Objects have tested equals. - * - * @see EqualsExclude - * @since 2.0 - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, - final String... excludeFields) { - return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, false, excludeFields); - } - /** * This method uses reflection to determine if the two {@link Object}s * are equal. @@ -450,6 +237,677 @@ public class EqualsBuilder implements Builder { .isEquals(); } + /** + * This method uses reflection to determine if the two {@link Object}s + * are equal. + * + *

It uses {@code 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. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

If the testTransients parameter is set to {@code true}, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the {@link 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 {@code 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 {@code null} + * @param excludeFields array of field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + * @since 2.0 + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, + final String... excludeFields) { + return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, false, excludeFields); + } + + /** + * This method uses reflection to determine if the two {@link Object}s + * are equal. + * + *

It uses {@code 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. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

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 {@code this} object + * @param rhs the other object + * @param excludeFields Collection of String field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields)); + } + /** + * This method uses reflection to determine if the two {@link Object}s + * are equal. + * + *

It uses {@code 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. Non-primitive fields are compared using + * {@code equals()}.

+ * + *

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 {@code this} object + * @param rhs the other object + * @param excludeFields array of field names to exclude from testing + * @return {@code true} if the two Objects have tested equals. + * + * @see EqualsExclude + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } + /** + * Registers the given object pair. + * Used by the reflection methods to avoid infinite loops. + * + * @param lhs {@code this} object to register + * @param rhs the other object to register + */ + private static void register(final Object lhs, final Object rhs) { + Set> registry = getRegistry(); + if (registry == null) { + registry = new HashSet<>(); + REGISTRY.set(registry); + } + final Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } + /** + * Unregisters the given object pair. + * + *

+ * Used by the reflection methods to avoid infinite loops. + * + * @param lhs {@code this} object to unregister + * @param rhs the other object to unregister + * @since 3.0 + */ + private static void unregister(final Object lhs, final Object rhs) { + final Set> registry = getRegistry(); + if (registry != null) { + registry.remove(getRegisterPair(lhs, rhs)); + if (registry.isEmpty()) { + REGISTRY.remove(); + } + } + } + /** + * If the fields tested are equals. + * The default value is {@code true}. + */ + private boolean isEquals = true; + + private boolean testTransients; + + private boolean testRecursive; + + private List> bypassReflectionClasses; + + private Class reflectUpToClass; + + private String[] excludeFields; + + /** + * Constructor for EqualsBuilder. + * + *

Starts off assuming that equals is {@code true}.

+ * @see Object#equals(Object) + */ + public EqualsBuilder() { + // set up default classes to bypass reflection for + bypassReflectionClasses = new ArrayList<>(1); + bypassReflectionClasses.add(String.class); //hashCode field being lazy but not transient + } + + + /** + * Test if two {@code booleans}s are equal. + * + * @param lhs the left-hand {@code boolean} + * @param rhs the right-hand {@code boolean} + * @return this + */ + public EqualsBuilder append(final boolean lhs, final boolean rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code boolean}. Length and all + * values are compared. + * + *

The method {@link #append(boolean, boolean)} is used.

+ * + * @param lhs the left-hand {@code boolean[]} + * @param rhs the right-hand {@code boolean[]} + * @return this + */ + public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code byte}s are equal. + * + * @param lhs the left-hand {@code byte} + * @param rhs the right-hand {@code byte} + * @return this + */ + public EqualsBuilder append(final byte lhs, final byte rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code byte}. Length and all + * values are compared. + * + *

The method {@link #append(byte, byte)} is used.

+ * + * @param lhs the left-hand {@code byte[]} + * @param rhs the right-hand {@code byte[]} + * @return this + */ + public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code char}s are equal. + * + * @param lhs the left-hand {@code char} + * @param rhs the right-hand {@code char} + * @return this + */ + public EqualsBuilder append(final char lhs, final char rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code char}. Length and all + * values are compared. + * + *

The method {@link #append(char, char)} is used.

+ * + * @param lhs the left-hand {@code char[]} + * @param rhs the right-hand {@code char[]} + * @return this + */ + public EqualsBuilder append(final char[] lhs, final char[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code double}s are equal by testing that the + * pattern of bits returned by {@code doubleToLong} are equal. + * + *

This handles NaNs, Infinities, and {@code -0.0}.

+ * + *

It is compatible with the hash code generated by + * {@link HashCodeBuilder}.

+ * + * @param lhs the left-hand {@code double} + * @param rhs the right-hand {@code double} + * @return this + */ + public EqualsBuilder append(final double lhs, final double rhs) { + if (!isEquals) { + return this; + } + return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); + } + + /** + * Deep comparison of array of {@code double}. Length and all + * values are compared. + * + *

The method {@link #append(double, double)} is used.

+ * + * @param lhs the left-hand {@code double[]} + * @param rhs the right-hand {@code double[]} + * @return this + */ + public EqualsBuilder append(final double[] lhs, final double[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code float}s are equal by testing that the + * pattern of bits returned by doubleToLong are equal. + * + *

This handles NaNs, Infinities, and {@code -0.0}.

+ * + *

It is compatible with the hash code generated by + * {@link HashCodeBuilder}.

+ * + * @param lhs the left-hand {@code float} + * @param rhs the right-hand {@code float} + * @return this + */ + public EqualsBuilder append(final float lhs, final float rhs) { + if (!isEquals) { + return this; + } + return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); + } + + /** + * Deep comparison of array of {@code float}. Length and all + * values are compared. + * + *

The method {@link #append(float, float)} is used.

+ * + * @param lhs the left-hand {@code float[]} + * @param rhs the right-hand {@code float[]} + * @return this + */ + public EqualsBuilder append(final float[] lhs, final float[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code int}s are equal. + * + * @param lhs the left-hand {@code int} + * @param rhs the right-hand {@code int} + * @return this + */ + public EqualsBuilder append(final int lhs, final int rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code int}. Length and all + * values are compared. + * + *

The method {@link #append(int, int)} is used.

+ * + * @param lhs the left-hand {@code int[]} + * @param rhs the right-hand {@code int[]} + * @return this + */ + public EqualsBuilder append(final int[] lhs, final int[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code long} s are equal. + * + * @param lhs + * the left-hand {@code long} + * @param rhs + * the right-hand {@code long} + * @return this + */ + public EqualsBuilder append(final long lhs, final long rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code long}. Length and all + * values are compared. + * + *

The method {@link #append(long, long)} is used.

+ * + * @param lhs the left-hand {@code long[]} + * @param rhs the right-hand {@code long[]} + * @return this + */ + public EqualsBuilder append(final long[] lhs, final long[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@link Object}s are equal using either + * #{@link #reflectionAppend(Object, Object)}, if object are non + * primitives (or wrapper of primitives) or if field {@code testRecursive} + * is set to {@code false}. Otherwise, using their + * {@code equals} method. + * + * @param lhs the left-hand object + * @param rhs the right-hand object + * @return this + */ + public EqualsBuilder append(final Object lhs, final Object rhs) { + if (!isEquals) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + final Class lhsClass = lhs.getClass(); + if (lhsClass.isArray()) { + // factor out array case in order to keep method small enough + // to be inlined + appendArray(lhs, rhs); + } else // The simple case, not an array, just test the element + if (testRecursive && !ClassUtils.isPrimitiveOrWrapper(lhsClass)) { + reflectionAppend(lhs, rhs); + } else { + isEquals = lhs.equals(rhs); + } + return this; + } + + /** + * Performs a deep comparison of two {@link Object} arrays. + * + *

This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

+ * + *

Note that this method does not compare the type of the arrays; it only + * compares the contents.

+ * + * @param lhs the left-hand {@code Object[]} + * @param rhs the right-hand {@code Object[]} + * @return this + */ + public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if two {@code short}s are equal. + * + * @param lhs the left-hand {@code short} + * @param rhs the right-hand {@code short} + * @return this + */ + public EqualsBuilder append(final short lhs, final short rhs) { + if (!isEquals) { + return this; + } + isEquals = lhs == rhs; + return this; + } + + /** + * Deep comparison of array of {@code short}. Length and all + * values are compared. + * + *

The method {@link #append(short, short)} is used.

+ * + * @param lhs the left-hand {@code short[]} + * @param rhs the right-hand {@code short[]} + * @return this + */ + public EqualsBuilder append(final short[] lhs, final short[] rhs) { + if (!isEquals) { + 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; + } + + /** + * Test if an {@link Object} is equal to an array. + * + * @param lhs the left-hand object, an array + * @param rhs the right-hand object + */ + private void appendArray(final Object lhs, final Object rhs) { + // First we compare different dimensions, for example: a boolean[][] to a boolean[] + // then we 'Switch' on type of array, to dispatch to the correct handler + // This handles multidimensional arrays of the same depth + if (lhs.getClass() != rhs.getClass()) { + this.setEquals(false); + } 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); + } + } + + /** + * Adds the result of {@code super.equals()} to this builder. + * + * @param superEquals the result of calling {@code super.equals()} + * @return this + * @since 2.0 + */ + public EqualsBuilder appendSuper(final boolean superEquals) { + if (!isEquals) { + return this; + } + isEquals = superEquals; + return this; + } + + /** + * Returns {@code true} if the fields that have been checked + * are all equal. + * + * @return {@code true} if all of the fields that have been checked + * are equal, {@code false} otherwise. + * + * @since 3.0 + */ + @Override + public Boolean build() { + return Boolean.valueOf(isEquals()); + } + + /** + * Returns {@code true} if the fields that have been checked + * are all equal. + * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } + /** * Tests if two {@code objects} by using reflection. * @@ -576,528 +1034,31 @@ public class EqualsBuilder implements Builder { } /** - * Adds the result of {@code super.equals()} to this builder. - * - * @param superEquals the result of calling {@code super.equals()} - * @return this - * @since 2.0 + * Reset the EqualsBuilder so you can use the same object again + * @since 2.5 */ - public EqualsBuilder appendSuper(final boolean superEquals) { - if (!isEquals) { - return this; - } - isEquals = superEquals; + public void reset() { + this.isEquals = true; + } + + /** + * Sets {@link Class}es whose instances should be compared by calling their {@code equals} + * although being in recursive mode. So the fields of theses classes will not be compared recursively by reflection. + * + *

Here you should name classes having non-transient fields which are cache fields being set lazily.
+ * Prominent example being {@link String} class with its hash code cache field. Due to the importance + * of the {@link String} class, it is included in the default bypasses classes. Usually, if you use + * your own set of classes here, remember to include {@link String} class, too.

+ * @param bypassReflectionClasses classes to bypass reflection test + * @return this + * @see #setTestRecursive(boolean) + * @since 3.8 + */ + public EqualsBuilder setBypassReflectionClasses(final List> bypassReflectionClasses) { + this.bypassReflectionClasses = bypassReflectionClasses; return this; } - /** - * Test if two {@link Object}s are equal using either - * #{@link #reflectionAppend(Object, Object)}, if object are non - * primitives (or wrapper of primitives) or if field {@code testRecursive} - * is set to {@code false}. Otherwise, using their - * {@code equals} method. - * - * @param lhs the left-hand object - * @param rhs the right-hand object - * @return this - */ - public EqualsBuilder append(final Object lhs, final Object rhs) { - if (!isEquals) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - final Class lhsClass = lhs.getClass(); - if (lhsClass.isArray()) { - // factor out array case in order to keep method small enough - // to be inlined - appendArray(lhs, rhs); - } else // The simple case, not an array, just test the element - if (testRecursive && !ClassUtils.isPrimitiveOrWrapper(lhsClass)) { - reflectionAppend(lhs, rhs); - } else { - isEquals = lhs.equals(rhs); - } - return this; - } - - /** - * Test if an {@link Object} is equal to an array. - * - * @param lhs the left-hand object, an array - * @param rhs the right-hand object - */ - private void appendArray(final Object lhs, final Object rhs) { - // First we compare different dimensions, for example: a boolean[][] to a boolean[] - // then we 'Switch' on type of array, to dispatch to the correct handler - // This handles multidimensional arrays of the same depth - if (lhs.getClass() != rhs.getClass()) { - this.setEquals(false); - } 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); - } - } - - /** - * Test if two {@code long} s are equal. - * - * @param lhs - * the left-hand {@code long} - * @param rhs - * the right-hand {@code long} - * @return this - */ - public EqualsBuilder append(final long lhs, final long rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Test if two {@code int}s are equal. - * - * @param lhs the left-hand {@code int} - * @param rhs the right-hand {@code int} - * @return this - */ - public EqualsBuilder append(final int lhs, final int rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Test if two {@code short}s are equal. - * - * @param lhs the left-hand {@code short} - * @param rhs the right-hand {@code short} - * @return this - */ - public EqualsBuilder append(final short lhs, final short rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Test if two {@code char}s are equal. - * - * @param lhs the left-hand {@code char} - * @param rhs the right-hand {@code char} - * @return this - */ - public EqualsBuilder append(final char lhs, final char rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Test if two {@code byte}s are equal. - * - * @param lhs the left-hand {@code byte} - * @param rhs the right-hand {@code byte} - * @return this - */ - public EqualsBuilder append(final byte lhs, final byte rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Test if two {@code double}s are equal by testing that the - * pattern of bits returned by {@code doubleToLong} are equal. - * - *

This handles NaNs, Infinities, and {@code -0.0}.

- * - *

It is compatible with the hash code generated by - * {@link HashCodeBuilder}.

- * - * @param lhs the left-hand {@code double} - * @param rhs the right-hand {@code double} - * @return this - */ - public EqualsBuilder append(final double lhs, final double rhs) { - if (!isEquals) { - return this; - } - return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); - } - - /** - * Test if two {@code float}s are equal by testing that the - * pattern of bits returned by doubleToLong are equal. - * - *

This handles NaNs, Infinities, and {@code -0.0}.

- * - *

It is compatible with the hash code generated by - * {@link HashCodeBuilder}.

- * - * @param lhs the left-hand {@code float} - * @param rhs the right-hand {@code float} - * @return this - */ - public EqualsBuilder append(final float lhs, final float rhs) { - if (!isEquals) { - return this; - } - return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); - } - - /** - * Test if two {@code booleans}s are equal. - * - * @param lhs the left-hand {@code boolean} - * @param rhs the right-hand {@code boolean} - * @return this - */ - public EqualsBuilder append(final boolean lhs, final boolean rhs) { - if (!isEquals) { - return this; - } - isEquals = lhs == rhs; - return this; - } - - /** - * Performs a deep comparison of two {@link Object} arrays. - * - *

This also will be called for the top level of - * multi-dimensional, ragged, and multi-typed arrays.

- * - *

Note that this method does not compare the type of the arrays; it only - * compares the contents.

- * - * @param lhs the left-hand {@code Object[]} - * @param rhs the right-hand {@code Object[]} - * @return this - */ - public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { - if (!isEquals) { - 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 {@code long}. Length and all - * values are compared. - * - *

The method {@link #append(long, long)} is used.

- * - * @param lhs the left-hand {@code long[]} - * @param rhs the right-hand {@code long[]} - * @return this - */ - public EqualsBuilder append(final long[] lhs, final long[] rhs) { - if (!isEquals) { - 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 {@code int}. Length and all - * values are compared. - * - *

The method {@link #append(int, int)} is used.

- * - * @param lhs the left-hand {@code int[]} - * @param rhs the right-hand {@code int[]} - * @return this - */ - public EqualsBuilder append(final int[] lhs, final int[] rhs) { - if (!isEquals) { - 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 {@code short}. Length and all - * values are compared. - * - *

The method {@link #append(short, short)} is used.

- * - * @param lhs the left-hand {@code short[]} - * @param rhs the right-hand {@code short[]} - * @return this - */ - public EqualsBuilder append(final short[] lhs, final short[] rhs) { - if (!isEquals) { - 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 {@code char}. Length and all - * values are compared. - * - *

The method {@link #append(char, char)} is used.

- * - * @param lhs the left-hand {@code char[]} - * @param rhs the right-hand {@code char[]} - * @return this - */ - public EqualsBuilder append(final char[] lhs, final char[] rhs) { - if (!isEquals) { - 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 {@code byte}. Length and all - * values are compared. - * - *

The method {@link #append(byte, byte)} is used.

- * - * @param lhs the left-hand {@code byte[]} - * @param rhs the right-hand {@code byte[]} - * @return this - */ - public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { - if (!isEquals) { - 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 {@code double}. Length and all - * values are compared. - * - *

The method {@link #append(double, double)} is used.

- * - * @param lhs the left-hand {@code double[]} - * @param rhs the right-hand {@code double[]} - * @return this - */ - public EqualsBuilder append(final double[] lhs, final double[] rhs) { - if (!isEquals) { - 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 {@code float}. Length and all - * values are compared. - * - *

The method {@link #append(float, float)} is used.

- * - * @param lhs the left-hand {@code float[]} - * @param rhs the right-hand {@code float[]} - * @return this - */ - public EqualsBuilder append(final float[] lhs, final float[] rhs) { - if (!isEquals) { - 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 {@code boolean}. Length and all - * values are compared. - * - *

The method {@link #append(boolean, boolean)} is used.

- * - * @param lhs the left-hand {@code boolean[]} - * @param rhs the right-hand {@code boolean[]} - * @return this - */ - public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { - if (!isEquals) { - 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 {@code true} if the fields that have been checked - * are all equal. - * - * @return boolean - */ - public boolean isEquals() { - return this.isEquals; - } - - /** - * Returns {@code true} if the fields that have been checked - * are all equal. - * - * @return {@code true} if all of the fields that have been checked - * are equal, {@code false} otherwise. - * - * @since 3.0 - */ - @Override - public Boolean build() { - return Boolean.valueOf(isEquals()); - } - /** * Sets the {@code isEquals} value. * @@ -1109,10 +1070,49 @@ public class EqualsBuilder implements Builder { } /** - * Reset the EqualsBuilder so you can use the same object again - * @since 2.5 + * Sets field names to be excluded by reflection tests. + * @param excludeFields the fields to exclude + * @return this + * @since 3.6 */ - public void reset() { - this.isEquals = true; + public EqualsBuilder setExcludeFields(final String... excludeFields) { + this.excludeFields = excludeFields; + return this; + } + + /** + * Sets the superclass to reflect up to at reflective tests. + * @param reflectUpToClass the super class to reflect up to + * @return this + * @since 3.6 + */ + public EqualsBuilder setReflectUpToClass(final Class reflectUpToClass) { + this.reflectUpToClass = reflectUpToClass; + return this; + } + + /** + * Sets whether to test fields recursively, instead of using their equals method, when reflectively comparing objects. + * String objects, which cache a hash value, are automatically excluded from recursive testing. + * You may specify other exceptions by calling {@link #setBypassReflectionClasses(List)}. + * @param testRecursive whether to do a recursive test + * @return this + * @see #setBypassReflectionClasses(List) + * @since 3.6 + */ + public EqualsBuilder setTestRecursive(final boolean testRecursive) { + this.testRecursive = testRecursive; + return this; + } + + /** + * Sets whether to include transient fields when reflectively comparing objects. + * @param testTransients whether to test transient fields + * @return this + * @since 3.6 + */ + public EqualsBuilder setTestTransients(final boolean testTransients) { + this.testTransients = testTransients; + return this; } } diff --git a/src/main/java/org/apache/commons/lang3/builder/IDKey.java b/src/main/java/org/apache/commons/lang3/builder/IDKey.java index 8fabb482d..563f7c0d2 100644 --- a/src/main/java/org/apache/commons/lang3/builder/IDKey.java +++ b/src/main/java/org/apache/commons/lang3/builder/IDKey.java @@ -43,15 +43,6 @@ final class IDKey { this.value = value; } - /** - * returns hash code - i.e. the system identity hash code. - * @return the hash code - */ - @Override - public int hashCode() { - return id; - } - /** * checks if instances are equal * @param other The other object to compare to @@ -69,4 +60,13 @@ final class IDKey { // Note that identity equals is used. return value == idKey.value; } + + /** + * returns hash code - i.e. the system identity hash code. + * @return the hash code + */ + @Override + public int hashCode() { + return id; + } } diff --git a/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java index 443282cd1..285d3b791 100644 --- a/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java @@ -82,86 +82,8 @@ public class MultilineRecursiveToStringStyle extends RecursiveToStringStyle { resetIndent(); } - /** - * Resets the fields responsible for the line breaks and indenting. - * Must be invoked after changing the {@link #spaces} value. - */ - private void resetIndent() { - setArrayStart("{" + System.lineSeparator() + spacer(spaces)); - setArraySeparator("," + System.lineSeparator() + spacer(spaces)); - setArrayEnd(System.lineSeparator() + spacer(spaces - INDENT) + "}"); - - setContentStart("[" + System.lineSeparator() + spacer(spaces)); - setFieldSeparator("," + System.lineSeparator() + spacer(spaces)); - setContentEnd(System.lineSeparator() + spacer(spaces - INDENT) + "]"); - } - - /** - * Creates a StringBuilder responsible for the indenting. - * - * @param spaces how far to indent - * @return a StringBuilder with {spaces} leading space characters. - */ - private StringBuilder spacer(final int spaces) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < spaces; i++) { - sb.append(" "); - } - return sb; - } - @Override - public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { - if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass()) - && accept(value.getClass())) { - spaces += INDENT; - resetIndent(); - buffer.append(ReflectionToStringBuilder.toString(value, this)); - spaces -= INDENT; - resetIndent(); - } else { - super.appendDetail(buffer, fieldName, value); - } - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { - spaces += INDENT; - resetIndent(); - super.appendDetail(buffer, fieldName, array); - spaces -= INDENT; - resetIndent(); - } - - @Override - protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { - spaces += INDENT; - resetIndent(); - super.reflectionAppendArrayDetail(buffer, fieldName, array); - spaces -= INDENT; - resetIndent(); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { - spaces += INDENT; - resetIndent(); - super.appendDetail(buffer, fieldName, array); - spaces -= INDENT; - resetIndent(); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { - spaces += INDENT; - resetIndent(); - super.appendDetail(buffer, fieldName, array); - spaces -= INDENT; - resetIndent(); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { spaces += INDENT; resetIndent(); super.appendDetail(buffer, fieldName, array); @@ -206,7 +128,7 @@ public class MultilineRecursiveToStringStyle extends RecursiveToStringStyle { } @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { spaces += INDENT; resetIndent(); super.appendDetail(buffer, fieldName, array); @@ -214,4 +136,82 @@ public class MultilineRecursiveToStringStyle extends RecursiveToStringStyle { resetIndent(); } + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { + spaces += INDENT; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= INDENT; + resetIndent(); + } + + @Override + public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass()) + && accept(value.getClass())) { + spaces += INDENT; + resetIndent(); + buffer.append(ReflectionToStringBuilder.toString(value, this)); + spaces -= INDENT; + resetIndent(); + } else { + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + spaces += INDENT; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= INDENT; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + spaces += INDENT; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= INDENT; + resetIndent(); + } + + @Override + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + spaces += INDENT; + resetIndent(); + super.reflectionAppendArrayDetail(buffer, fieldName, array); + spaces -= INDENT; + resetIndent(); + } + + /** + * Resets the fields responsible for the line breaks and indenting. + * Must be invoked after changing the {@link #spaces} value. + */ + private void resetIndent() { + setArrayStart("{" + System.lineSeparator() + spacer(spaces)); + setArraySeparator("," + System.lineSeparator() + spacer(spaces)); + setArrayEnd(System.lineSeparator() + spacer(spaces - INDENT) + "}"); + + setContentStart("[" + System.lineSeparator() + spacer(spaces)); + setFieldSeparator("," + System.lineSeparator() + spacer(spaces)); + setContentEnd(System.lineSeparator() + spacer(spaces - INDENT) + "]"); + } + + /** + * Creates a StringBuilder responsible for the indenting. + * + * @param spaces how far to indent + * @return a StringBuilder with {spaces} leading space characters. + */ + private StringBuilder spacer(final int spaces) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < spaces; i++) { + sb.append(" "); + } + return sb; + } + } diff --git a/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java index 01b5e350c..f773848fc 100644 --- a/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java @@ -65,24 +65,6 @@ public class RecursiveToStringStyle extends ToStringStyle { public RecursiveToStringStyle() { } - @Override - public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { - if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && - !String.class.equals(value.getClass()) && - accept(value.getClass())) { - buffer.append(ReflectionToStringBuilder.toString(value, this)); - } else { - super.appendDetail(buffer, fieldName, value); - } - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { - appendClassName(buffer, coll); - appendIdentityHashCode(buffer, coll); - appendDetail(buffer, fieldName, coll.toArray()); - } - /** * Returns whether or not to recursively format the given {@link Class}. * By default, this method always returns {@code true}, but may be overwritten by @@ -95,4 +77,22 @@ public class RecursiveToStringStyle extends ToStringStyle { protected boolean accept(final Class clazz) { return true; } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { + appendClassName(buffer, coll); + appendIdentityHashCode(buffer, coll); + appendDetail(buffer, fieldName, coll.toArray()); + } + + @Override + public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && + !String.class.equals(value.getClass()) && + accept(value.getClass())) { + buffer.append(ReflectionToStringBuilder.toString(value, this)); + } else { + super.appendDetail(buffer, fieldName, value); + } + } } diff --git a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java index a5a8f9496..785e08d59 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java @@ -108,6 +108,49 @@ public class ReflectionDiffBuilder implements Builder> { this.diffBuilder = new DiffBuilder<>(lhs, rhs, style); } + private boolean accept(final Field field) { + if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) { + return false; + } + if (Modifier.isTransient(field.getModifiers())) { + return false; + } + if (Modifier.isStatic(field.getModifiers())) { + return false; + } + if (this.excludeFieldNames != null + && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) { + // Reject fields from the getExcludeFieldNames list. + return false; + } + return !field.isAnnotationPresent(DiffExclude.class); + } + + + private void appendFields(final Class clazz) { + for (final Field field : FieldUtils.getAllFields(clazz)) { + if (accept(field)) { + try { + diffBuilder.append(field.getName(), FieldUtils.readField(field, left, true), FieldUtils.readField(field, right, true)); + } catch (final IllegalAccessException e) { + // this can't happen. Would get a Security exception instead + // throw a runtime exception in case the impossible happens. + throw new IllegalArgumentException("Unexpected IllegalAccessException: " + e.getMessage(), e); + } + } + } + } + + @Override + public DiffResult build() { + if (left.equals(right)) { + return diffBuilder.build(); + } + + appendFields(left.getClass()); + return diffBuilder.build(); + } + /** * Gets the field names that should be excluded from the diff. * @@ -118,7 +161,6 @@ public class ReflectionDiffBuilder implements Builder> { return this.excludeFieldNames.clone(); } - /** * Sets the field names to exclude. * @@ -137,46 +179,4 @@ public class ReflectionDiffBuilder implements Builder> { return this; } - @Override - public DiffResult build() { - if (left.equals(right)) { - return diffBuilder.build(); - } - - appendFields(left.getClass()); - return diffBuilder.build(); - } - - private void appendFields(final Class clazz) { - for (final Field field : FieldUtils.getAllFields(clazz)) { - if (accept(field)) { - try { - diffBuilder.append(field.getName(), FieldUtils.readField(field, left, true), FieldUtils.readField(field, right, true)); - } catch (final IllegalAccessException e) { - // this can't happen. Would get a Security exception instead - // throw a runtime exception in case the impossible happens. - throw new IllegalArgumentException("Unexpected IllegalAccessException: " + e.getMessage(), e); - } - } - } - } - - private boolean accept(final Field field) { - if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) { - return false; - } - if (Modifier.isTransient(field.getModifiers())) { - return false; - } - if (Modifier.isStatic(field.getModifiers())) { - return false; - } - if (this.excludeFieldNames != null - && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) { - // Reject fields from the getExcludeFieldNames list. - return false; - } - return !field.isAnnotationPresent(DiffExclude.class); - } - } diff --git a/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java index ba49f0b47..de8fc28aa 100644 --- a/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java @@ -46,152 +46,6 @@ public class StandardToStringStyle extends ToStringStyle { public StandardToStringStyle() { } - /** - * Gets whether to use the class name. - * - * @return the current useClassName flag - */ - @Override - public boolean isUseClassName() { - return super.isUseClassName(); - } - - /** - * Sets whether to use the class name. - * - * @param useClassName the new useClassName flag - */ - @Override - public void setUseClassName(final boolean useClassName) { - super.setUseClassName(useClassName); - } - - /** - * Gets whether to output short or long class names. - * - * @return the current useShortClassName flag - * @since 2.0 - */ - @Override - public boolean isUseShortClassName() { - 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(final boolean useShortClassName) { - super.setUseShortClassName(useShortClassName); - } - - /** - * Gets whether to use the identity hash code. - * @return the current useIdentityHashCode flag - */ - @Override - public boolean isUseIdentityHashCode() { - return super.isUseIdentityHashCode(); - } - - /** - * Sets whether to use the identity hash code. - * - * @param useIdentityHashCode the new useIdentityHashCode flag - */ - @Override - public void setUseIdentityHashCode(final boolean useIdentityHashCode) { - super.setUseIdentityHashCode(useIdentityHashCode); - } - - /** - * Gets whether to use the field names passed in. - * - * @return the current useFieldNames flag - */ - @Override - public boolean isUseFieldNames() { - return super.isUseFieldNames(); - } - - /** - * Sets whether to use the field names passed in. - * - * @param useFieldNames the new useFieldNames flag - */ - @Override - public void setUseFieldNames(final boolean useFieldNames) { - super.setUseFieldNames(useFieldNames); - } - - /** - * Gets whether to use full detail when the caller doesn't - * specify. - * - * @return the current defaultFullDetail flag - */ - @Override - public boolean isDefaultFullDetail() { - 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(final boolean defaultFullDetail) { - super.setDefaultFullDetail(defaultFullDetail); - } - - /** - * Gets whether to output array content detail. - * - * @return the current array content detail setting - */ - @Override - public boolean isArrayContentDetail() { - return super.isArrayContentDetail(); - } - - /** - * Sets whether to output array content detail. - * - * @param arrayContentDetail the new arrayContentDetail flag - */ - @Override - public void setArrayContentDetail(final boolean arrayContentDetail) { - super.setArrayContentDetail(arrayContentDetail); - } - - /** - * Gets the array start text. - * - * @return the current array start text - */ - @Override - public String getArrayStart() { - return super.getArrayStart(); - } - - /** - * Sets the array start text. - * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param arrayStart the new array start text - */ - @Override - public void setArrayStart(final String arrayStart) { - super.setArrayStart(arrayStart); - } - /** * Gets the array end text. * @@ -202,19 +56,6 @@ public class StandardToStringStyle extends ToStringStyle { return super.getArrayEnd(); } - /** - * Sets the array end text. - * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param arrayEnd the new array end text - */ - @Override - public void setArrayEnd(final String arrayEnd) { - super.setArrayEnd(arrayEnd); - } - /** * Gets the array separator text. * @@ -226,39 +67,13 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Sets the array separator text. + * Gets the array start text. * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param arraySeparator the new array separator text + * @return the current array start text */ @Override - public void setArraySeparator(final String arraySeparator) { - super.setArraySeparator(arraySeparator); - } - - /** - * Gets the content start text. - * - * @return the current content start text - */ - @Override - public String getContentStart() { - return super.getContentStart(); - } - - /** - * Sets the content start text. - * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param contentStart the new content start text - */ - @Override - public void setContentStart(final String contentStart) { - super.setContentStart(contentStart); + public String getArrayStart() { + return super.getArrayStart(); } /** @@ -272,16 +87,13 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Sets the content end text. + * Gets the content start text. * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param contentEnd the new content end text + * @return the current content start text */ @Override - public void setContentEnd(final String contentEnd) { - super.setContentEnd(contentEnd); + public String getContentStart() { + return super.getContentStart(); } /** @@ -294,19 +106,6 @@ public class StandardToStringStyle extends ToStringStyle { return super.getFieldNameValueSeparator(); } - /** - * Sets the field name value separator text. - * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param fieldNameValueSeparator the new field name value separator text - */ - @Override - public void setFieldNameValueSeparator(final String fieldNameValueSeparator) { - super.setFieldNameValueSeparator(fieldNameValueSeparator); - } - /** * Gets the field separator text. * @@ -317,67 +116,6 @@ public class StandardToStringStyle extends ToStringStyle { return super.getFieldSeparator(); } - /** - * Sets the field separator text. - * - *

{@code null} is accepted, but will be converted - * to an empty String.

- * - * @param fieldSeparator the new field separator text - */ - @Override - public void setFieldSeparator(final String fieldSeparator) { - 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() { - 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(final boolean fieldSeparatorAtStart) { - 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() { - 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(final boolean fieldSeparatorAtEnd) { - super.setFieldSeparatorAtEnd(fieldSeparatorAtEnd); - } - /** * Gets the text to output when {@code null} found. * @@ -389,16 +127,16 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Sets the text to output when {@code null} found. + * Gets the end text to output when a {@link Collection}, + * {@link Map} or {@link Array} size is output. * - *

{@code null} is accepted, but will be converted - * to an empty String.

+ *

This is output after the size value.

* - * @param nullText the new text to output when {@code null} found + * @return the current end of size text */ @Override - public void setNullText(final String nullText) { - super.setNullText(nullText); + public String getSizeEndText() { + return super.getSizeEndText(); } /** @@ -415,32 +153,263 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Sets the start text to output when a {@link Collection}, - * {@link Map} or {@link Array} size is output. - * - *

This is output before the size value.

- * - *

{@code null} is accepted, but will be converted to - * an empty String.

- * - * @param sizeStartText the new start of size text - */ - @Override - public void setSizeStartText(final String sizeStartText) { - super.setSizeStartText(sizeStartText); - } - - /** - * Gets the end text to output when a {@link Collection}, - * {@link Map} or {@link Array} size is output. + * Gets the end text to output when an {@link Object} is + * output in summary mode. * *

This is output after the size value.

* - * @return the current end of size text + * @return the current end of summary text */ @Override - public String getSizeEndText() { - return super.getSizeEndText(); + public String getSummaryObjectEndText() { + return super.getSummaryObjectEndText(); + } + + /** + * Gets the start text to output when an {@link Object} is + * output in summary mode. + * + *

This is output before the size value.

+ * + * @return the current start of summary text + */ + @Override + public String getSummaryObjectStartText() { + return super.getSummaryObjectStartText(); + } + + /** + * Gets whether to output array content detail. + * + * @return the current array content detail setting + */ + @Override + public boolean isArrayContentDetail() { + return super.isArrayContentDetail(); + } + + /** + * Gets whether to use full detail when the caller doesn't + * specify. + * + * @return the current defaultFullDetail flag + */ + @Override + public boolean isDefaultFullDetail() { + return super.isDefaultFullDetail(); + } + + /** + * Gets whether the field separator should be added at the end + * of each buffer. + * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + @Override + public boolean isFieldSeparatorAtEnd() { + return super.isFieldSeparatorAtEnd(); + } + + /** + * 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() { + return super.isFieldSeparatorAtStart(); + } + + /** + * Gets whether to use the class name. + * + * @return the current useClassName flag + */ + @Override + public boolean isUseClassName() { + return super.isUseClassName(); + } + + /** + * Gets whether to use the field names passed in. + * + * @return the current useFieldNames flag + */ + @Override + public boolean isUseFieldNames() { + return super.isUseFieldNames(); + } + + /** + * Gets whether to use the identity hash code. + * @return the current useIdentityHashCode flag + */ + @Override + public boolean isUseIdentityHashCode() { + return super.isUseIdentityHashCode(); + } + + /** + * Gets whether to output short or long class names. + * + * @return the current useShortClassName flag + * @since 2.0 + */ + @Override + public boolean isUseShortClassName() { + return super.isUseShortClassName(); + } + + /** + * Sets whether to output array content detail. + * + * @param arrayContentDetail the new arrayContentDetail flag + */ + @Override + public void setArrayContentDetail(final boolean arrayContentDetail) { + super.setArrayContentDetail(arrayContentDetail); + } + + /** + * Sets the array end text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param arrayEnd the new array end text + */ + @Override + public void setArrayEnd(final String arrayEnd) { + super.setArrayEnd(arrayEnd); + } + + /** + * Sets the array separator text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param arraySeparator the new array separator text + */ + @Override + public void setArraySeparator(final String arraySeparator) { + super.setArraySeparator(arraySeparator); + } + + /** + * Sets the array start text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param arrayStart the new array start text + */ + @Override + public void setArrayStart(final String arrayStart) { + super.setArrayStart(arrayStart); + } + + /** + * Sets the content end text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param contentEnd the new content end text + */ + @Override + public void setContentEnd(final String contentEnd) { + super.setContentEnd(contentEnd); + } + + /** + * Sets the content start text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param contentStart the new content start text + */ + @Override + public void setContentStart(final String contentStart) { + super.setContentStart(contentStart); + } + + /** + * Sets whether to use full detail when the caller doesn't + * specify. + * + * @param defaultFullDetail the new defaultFullDetail flag + */ + @Override + public void setDefaultFullDetail(final boolean defaultFullDetail) { + super.setDefaultFullDetail(defaultFullDetail); + } + + /** + * Sets the field name value separator text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param fieldNameValueSeparator the new field name value separator text + */ + @Override + public void setFieldNameValueSeparator(final String fieldNameValueSeparator) { + super.setFieldNameValueSeparator(fieldNameValueSeparator); + } + + /** + * Sets the field separator text. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param fieldSeparator the new field separator text + */ + @Override + public void setFieldSeparator(final String fieldSeparator) { + super.setFieldSeparator(fieldSeparator); + } + + /** + * 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(final boolean fieldSeparatorAtEnd) { + super.setFieldSeparatorAtEnd(fieldSeparatorAtEnd); + } + + /** + * 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(final boolean fieldSeparatorAtStart) { + super.setFieldSeparatorAtStart(fieldSeparatorAtStart); + } + + /** + * Sets the text to output when {@code null} found. + * + *

{@code null} is accepted, but will be converted + * to an empty String.

+ * + * @param nullText the new text to output when {@code null} found + */ + @Override + public void setNullText(final String nullText) { + super.setNullText(nullText); } /** @@ -460,16 +429,35 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Gets the start text to output when an {@link Object} is - * output in summary mode. + * Sets the start text to output when a {@link Collection}, + * {@link Map} or {@link Array} size is output. * *

This is output before the size value.

* - * @return the current start of summary text + *

{@code null} is accepted, but will be converted to + * an empty String.

+ * + * @param sizeStartText the new start of size text */ @Override - public String getSummaryObjectStartText() { - return super.getSummaryObjectStartText(); + public void setSizeStartText(final String sizeStartText) { + super.setSizeStartText(sizeStartText); + } + + /** + * Sets the end text to output when an {@link Object} is + * output in summary mode. + * + *

This is output after the size value.

+ * + *

{@code null} is accepted, but will be converted to + * an empty String.

+ * + * @param summaryObjectEndText the new end of summary text + */ + @Override + public void setSummaryObjectEndText(final String summaryObjectEndText) { + super.setSummaryObjectEndText(summaryObjectEndText); } /** @@ -489,32 +477,44 @@ public class StandardToStringStyle extends ToStringStyle { } /** - * Gets the end text to output when an {@link Object} is - * output in summary mode. + * Sets whether to use the class name. * - *

This is output after the size value.

- * - * @return the current end of summary text + * @param useClassName the new useClassName flag */ @Override - public String getSummaryObjectEndText() { - return super.getSummaryObjectEndText(); + public void setUseClassName(final boolean useClassName) { + super.setUseClassName(useClassName); } /** - * Sets the end text to output when an {@link Object} is - * output in summary mode. + * Sets whether to use the field names passed in. * - *

This is output after the size value.

- * - *

{@code null} is accepted, but will be converted to - * an empty String.

- * - * @param summaryObjectEndText the new end of summary text + * @param useFieldNames the new useFieldNames flag */ @Override - public void setSummaryObjectEndText(final String summaryObjectEndText) { - super.setSummaryObjectEndText(summaryObjectEndText); + public void setUseFieldNames(final boolean useFieldNames) { + super.setUseFieldNames(useFieldNames); + } + + /** + * Sets whether to use the identity hash code. + * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + @Override + public void setUseIdentityHashCode(final boolean useIdentityHashCode) { + super.setUseIdentityHashCode(useIdentityHashCode); + } + + /** + * Sets whether to output short or long class names. + * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + @Override + public void setUseShortClassName(final boolean useShortClassName) { + super.setUseShortClassName(useShortClassName); } } diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java index afc32e25d..f08eaa45e 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java @@ -116,25 +116,6 @@ public class ToStringBuilder implements Builder { return defaultStyle; } - /** - * Sets the default {@link 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 {@link ToStringStyle} to the constructor instead - * of changing this global default.

- * - *

This method is not intended for use from multiple threads. - * Internally, a {@code volatile} variable is used to provide the guarantee - * that the latest value set is the value returned from {@link #getDefaultStyle}.

- * - * @param style the default {@link ToStringStyle} - * @throws NullPointerException if the style is {@code null} - */ - public static void setDefaultStyle(final ToStringStyle style) { - defaultStyle = Objects.requireNonNull(style, "style"); - } - /** * Uses {@link ReflectionToStringBuilder} to generate a * {@code toString} for the specified object. @@ -195,6 +176,25 @@ public class ToStringBuilder implements Builder { return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass); } + /** + * Sets the default {@link 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 {@link ToStringStyle} to the constructor instead + * of changing this global default.

+ * + *

This method is not intended for use from multiple threads. + * Internally, a {@code volatile} variable is used to provide the guarantee + * that the latest value set is the value returned from {@link #getDefaultStyle}.

+ * + * @param style the default {@link ToStringStyle} + * @throws NullPointerException if the style is {@code null} + */ + public static void setDefaultStyle(final ToStringStyle style) { + defaultStyle = Objects.requireNonNull(style, "style"); + } + /** * Current toString buffer, not null. */ @@ -968,6 +968,21 @@ public class ToStringBuilder implements Builder { return this; } + /** + * Returns the String that was build as an object representation. The + * default implementation utilizes the {@link #toString()} implementation. + * + * @return the String {@code toString} + * + * @see #toString() + * + * @since 3.0 + */ + @Override + public String build() { + return toString(); + } + /** * Returns the {@link Object} being output. * @@ -1016,19 +1031,4 @@ public class ToStringBuilder implements Builder { } return this.getStringBuffer().toString(); } - - /** - * Returns the String that was build as an object representation. The - * default implementation utilizes the {@link #toString()} implementation. - * - * @return the String {@code toString} - * - * @see #toString() - * - * @since 3.0 - */ - @Override - public String build() { - return toString(); - } } diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java index 5e8e06fce..d395567c7 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java @@ -68,6 +68,528 @@ import org.apache.commons.lang3.StringUtils; @SuppressWarnings("deprecation") // StringEscapeUtils public abstract class ToStringStyle implements Serializable { + /** + * Default {@link ToStringStyle}. + * + *

This is an inner class rather than using + * {@link 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() { + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return DEFAULT_STYLE; + } + + } + + /** + * {@link ToStringStyle} that outputs with JSON format. + * + *

+ * This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability. + *

+ * + * @since 3.4 + * @see json.org + */ + private static final class JsonToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + private static final String FIELD_NAME_QUOTE = "\""; + + /** + * Constructor. + * + *

+ * Use the static constant rather than instantiating. + *

+ */ + JsonToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + + this.setContentStart("{"); + this.setContentEnd("}"); + + this.setArrayStart("["); + this.setArrayEnd("]"); + + this.setFieldSeparator(","); + this.setFieldNameValueSeparator(":"); + + this.setNullText("null"); + + this.setSummaryObjectStartText("\"<"); + this.setSummaryObjectEndText(">\""); + + this.setSizeStartText("\"\""); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final boolean[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final byte[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final char[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final double[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final float[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final int[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final long[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final Object value, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, value, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final Object[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final short[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { + appendValueAsString(buffer, String.valueOf(value)); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { + if (coll != null && !coll.isEmpty()) { + buffer.append(getArrayStart()); + int i = 0; + for (final Object item : coll) { + appendDetail(buffer, fieldName, i++, item); + } + buffer.append(getArrayEnd()); + return; + } + + buffer.append(coll); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map map) { + if (map != null && !map.isEmpty()) { + buffer.append(getContentStart()); + + boolean firstItem = true; + for (final Entry entry : map.entrySet()) { + final String keyStr = Objects.toString(entry.getKey(), null); + if (keyStr != null) { + if (firstItem) { + firstItem = false; + } else { + appendFieldEnd(buffer, keyStr); + } + appendFieldStart(buffer, keyStr); + final Object value = entry.getValue(); + if (value == null) { + appendNullText(buffer, keyStr); + } else { + appendInternal(buffer, keyStr, value, true); + } + } + } + + buffer.append(getContentEnd()); + return; + } + + buffer.append(map); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + + if (value == null) { + appendNullText(buffer, fieldName); + return; + } + + if (value instanceof String || value instanceof Character) { + appendValueAsString(buffer, value.toString()); + return; + } + + if (value instanceof Number || value instanceof Boolean) { + buffer.append(value); + return; + } + + final String valueAsString = value.toString(); + if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) { + buffer.append(value); + return; + } + + appendDetail(buffer, fieldName, valueAsString); + } + + @Override + protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + + super.appendFieldStart(buffer, FIELD_NAME_QUOTE + StringEscapeUtils.escapeJson(fieldName) + + FIELD_NAME_QUOTE); + } + + /** + * Appends the given String enclosed in double-quotes to the given StringBuffer. + * + * @param buffer the StringBuffer to append the value to. + * @param value the value to append. + */ + private void appendValueAsString(final StringBuffer buffer, final String value) { + buffer.append('"').append(StringEscapeUtils.escapeJson(value)).append('"'); + } + + private boolean isJsonArray(final String valueAsString) { + return valueAsString.startsWith(getArrayStart()) + && valueAsString.endsWith(getArrayEnd()); + } + + private boolean isJsonObject(final String valueAsString) { + return valueAsString.startsWith(getContentStart()) + && valueAsString.endsWith(getContentEnd()); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return JSON_STYLE; + } + + } + + /** + * {@link ToStringStyle} that outputs on multiple lines. + * + *

This is an inner class rather than using + * {@link 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() { + this.setContentStart("["); + this.setFieldSeparator(System.lineSeparator() + " "); + this.setFieldSeparatorAtStart(true); + this.setContentEnd(System.lineSeparator() + "]"); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return MULTI_LINE_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out the class name + * and identity hash code but prints content start and field names. + * + *

This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.

+ */ + private static final class NoClassNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + *

Use the static constant rather than instantiating.

+ */ + NoClassNameToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return NO_CLASS_NAME_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out + * the field names. + * + *

This is an inner class rather than using + * {@link 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() { + this.setUseFieldNames(false); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return NO_FIELD_NAMES_STYLE; + } + + } + + /** + * {@link ToStringStyle} that prints out the short + * class name and no identity hash code. + * + *

This is an inner class rather than using + * {@link 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() { + this.setUseShortClassName(true); + this.setUseIdentityHashCode(false); + } + + /** + * Ensure Singleton after serialization. + * @return the singleton + */ + private Object readResolve() { + return SHORT_PREFIX_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out the + * class name, identity hash code, content start or field name. + * + *

This is an inner class rather than using + * {@link 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() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + this.setUseFieldNames(false); + this.setContentStart(StringUtils.EMPTY); + this.setContentEnd(StringUtils.EMPTY); + } + + /** + * Ensure Singleton after serialization. + * @return the singleton + */ + private Object readResolve() { + return SIMPLE_STYLE; + } + + } + /** * Serialization version ID. */ @@ -348,85 +870,283 @@ public abstract class ToStringStyle implements Serializable { } /** - * Appends to the {@code toString} the superclass toString. - *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

- * - *

A {@code null} {@code superToString} is ignored.

+ * Appends to the {@code toString} a {@code boolean} + * value. * * @param buffer the {@link StringBuffer} to populate - * @param superToString the {@code super.toString()} - * @since 2.0 + * @param fieldName the field name + * @param value the value to add to the {@code toString} */ - public void appendSuper(final StringBuffer buffer, final String superToString) { - appendToString(buffer, superToString); + public void append(final StringBuffer buffer, final String fieldName, final boolean value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); } /** - * Appends to the {@code toString} another toString. - *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

- * - *

A {@code null} {@code toString} is ignored.

+ * Appends to the {@code toString} a {@code boolean} + * array. * * @param buffer the {@link StringBuffer} to populate - * @param toString the additional {@code toString} - * @since 2.0 + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides */ - public void appendToString(final StringBuffer buffer, final String toString) { - if (toString != null) { - final int pos1 = toString.indexOf(contentStart) + contentStart.length(); - final int pos2 = toString.lastIndexOf(contentEnd); - if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) { - if (fieldSeparatorAtStart) { - removeLastFieldSeparator(buffer); - } - buffer.append(toString, pos1, pos2); - appendFieldSeparator(buffer); - } + public void append(final StringBuffer buffer, final String fieldName, final boolean[] array, final 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); } /** - * Appends to the {@code toString} the start of data indicator. + * Appends to the {@code toString} a {@code byte} + * value. * * @param buffer the {@link StringBuffer} to populate - * @param object the {@link Object} to build a {@code toString} for + * @param fieldName the field name + * @param value the value to add to the {@code toString} */ - public void appendStart(final StringBuffer buffer, final Object object) { - if (object != null) { - appendClassName(buffer, object); - appendIdentityHashCode(buffer, object); - appendContentStart(buffer); - if (fieldSeparatorAtStart) { - appendFieldSeparator(buffer); - } - } + public void append(final StringBuffer buffer, final String fieldName, final byte value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); } /** - * Appends to the {@code toString} the end of data indicator. + * Appends to the {@code toString} a {@code byte} + * array. * * @param buffer the {@link StringBuffer} to populate - * @param object the {@link Object} to build a - * {@code toString} for. + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides */ - public void appendEnd(final StringBuffer buffer, final Object object) { - if (!this.fieldSeparatorAtEnd) { - removeLastFieldSeparator(buffer); + public void append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); } - appendContentEnd(buffer); - unregister(object); + + appendFieldEnd(buffer, fieldName); } /** - * Remove the last field separator from the buffer. + * Appends to the {@code toString} a {@code char} + * value. * * @param buffer the {@link StringBuffer} to populate - * @since 2.0 + * @param fieldName the field name + * @param value the value to add to the {@code toString} */ - protected void removeLastFieldSeparator(final StringBuffer buffer) { - if (StringUtils.endsWith(buffer, fieldSeparator)) { - buffer.setLength(buffer.length() - fieldSeparator.length()); + public void append(final StringBuffer buffer, final String fieldName, final char value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code char} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final char[] array, final 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); + } + + /** + * Appends to the {@code toString} a {@code double} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final double value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code double} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final double[] array, final 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); + } + + /** + * Appends to the {@code toString} a {@code float} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final float value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code float} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final float[] array, final 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); + } + + /** + * Appends to the {@code toString} an {@code int} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final int value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} an {@code int} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final int[] array, final 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); + } + + /** + *

Appends to the {@code toString} a {@code long} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final long value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code long} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final long[] array, final 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); } /** @@ -453,6 +1173,515 @@ public abstract class ToStringStyle implements Serializable { appendFieldEnd(buffer, fieldName); } + /** + * Appends to the {@code toString} an {@link Object} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final Object[] array, final 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); + } + + /** + * Appends to the {@code toString} a {@code short} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final short value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code short} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final short[] array, final 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); + } + + /** + * Appends to the {@code toString} the class name. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} whose name to output + */ + protected void appendClassName(final StringBuffer buffer, final Object object) { + if (useClassName && object != null) { + register(object); + if (useShortClassName) { + buffer.append(getShortClassName(object.getClass())); + } else { + buffer.append(object.getClass().getName()); + } + } + } + + /** + * Appends to the {@code toString} the content end. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendContentEnd(final StringBuffer buffer) { + buffer.append(contentEnd); + } + + /** + * Appends to the {@code toString} the content start. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendContentStart(final StringBuffer buffer) { + buffer.append(contentStart); + } + + /** + * Appends to the {@code toString} an {@link 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 {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + * + * @since 2.2 + */ + protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) { + ObjectUtils.identityToString(buffer, value); + } + + /** + * Appends to the {@code toString} a {@code boolean} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code boolean} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@code byte} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code byte} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@code char} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code char} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@link Collection}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param coll the {@link Collection} to add to the + * {@code toString}, not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { + buffer.append(coll); + } + + /** + * Appends to the {@code toString} a {@code double} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code double} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@code float} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code float} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} an {@code int} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of an + * {@link Object} array item. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param i the array item index to add + * @param item the array item to add + * @since 3.11 + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int i, final Object item) { + if (i > 0) { + buffer.append(arraySeparator); + } + if (item == null) { + appendNullText(buffer, fieldName); + } else { + appendInternal(buffer, fieldName, item, arrayContentDetail); + } + } + + /** + * Appends to the {@code toString} the detail of an + * {@code int} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@code long} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code long} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} a {@link Map}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param map the {@link Map} to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map map) { + buffer.append(map); + } + + /** + * Appends to the {@code toString} an {@link Object} + * value, printing the full detail of the {@link Object}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of an + * {@link Object} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + appendDetail(buffer, fieldName, i, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a {@code short} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code short} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); + } + + /** + * Appends to the {@code toString} the end of data indicator. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} to build a + * {@code toString} for. + */ + public void appendEnd(final StringBuffer buffer, final Object object) { + if (!this.fieldSeparatorAtEnd) { + removeLastFieldSeparator(buffer); + } + appendContentEnd(buffer); + unregister(object); + } + + /** + * Appends to the {@code toString} the field end. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendFieldEnd(final StringBuffer buffer, final String fieldName) { + appendFieldSeparator(buffer); + } + + /** + * Appends to the {@code toString} the field separator. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendFieldSeparator(final StringBuffer buffer) { + buffer.append(fieldSeparator); + } + + /** + * Appends to the {@code toString} the field start. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + */ + protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { + if (useFieldNames && fieldName != null) { + buffer.append(fieldName); + buffer.append(fieldNameValueSeparator); + } + } + + /** + * Appends the {@link System#identityHashCode(java.lang.Object)}. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} whose id to output + */ + protected void appendIdentityHashCode(final StringBuffer buffer, final Object object) { + if (this.isUseIdentityHashCode() && object != null) { + register(object); + buffer.append('@'); + buffer.append(ObjectUtils.identityHashCodeHex(object)); + } + } + /** * Appends to the {@code toString} an {@link Object}, * correctly interpreting its type. @@ -570,56 +1799,123 @@ public abstract class ToStringStyle implements Serializable { } /** - * Appends to the {@code toString} an {@link Object} - * value that has been detected to participate in a cycle. This - * implementation will print the standard string value of the value. + * Appends to the {@code toString} an indicator for {@code null}. + * + *

The default indicator is {@code '<null>'}.

* * @param buffer the {@link StringBuffer} to populate * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString}, - * not {@code null} - * - * @since 2.2 */ - protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) { - ObjectUtils.identityToString(buffer, value); + protected void appendNullText(final StringBuffer buffer, final String fieldName) { + buffer.append(nullText); } /** - * Appends to the {@code toString} an {@link Object} - * value, printing the full detail of the {@link Object}. + * Appends to the {@code toString} the start of data indicator. * * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString}, - * not {@code null} + * @param object the {@link Object} to build a {@code toString} for */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { - buffer.append(value); + public void appendStart(final StringBuffer buffer, final Object object) { + if (object != null) { + appendClassName(buffer, object); + appendIdentityHashCode(buffer, object); + appendContentStart(buffer); + if (fieldSeparatorAtStart) { + appendFieldSeparator(buffer); + } + } } /** - * Appends to the {@code toString} a {@link Collection}. + * Appends to the {@code toString} a summary of a + * {@code boolean} array. * * @param buffer the {@link StringBuffer} to populate * @param fieldName the field name, typically not used as already appended - * @param coll the {@link Collection} to add to the - * {@code toString}, not {@code null} + * @param array the array to add to the {@code toString}, + * not {@code null} */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { - buffer.append(coll); + protected void appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array) { + appendSummarySize(buffer, fieldName, array.length); } /** - * Appends to the {@code toString} a {@link Map}. + * Appends to the {@code toString} a summary of a + * {@code byte} array. * * @param buffer the {@link StringBuffer} to populate * @param fieldName the field name, typically not used as already appended - * @param map the {@link Map} to add to the {@code toString}, + * @param array the array to add to the {@code toString}, * not {@code null} */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map map) { - buffer.append(map); + protected void appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code char} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final char[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code double} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final double[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code float} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final float[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a summary of an + * {@code int} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final int[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code long} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final long[] array) { + appendSummarySize(buffer, fieldName, array.length); } /** @@ -637,296 +1933,6 @@ public abstract class ToStringStyle implements Serializable { buffer.append(summaryObjectEndText); } - /** - *

Appends to the {@code toString} a {@code long} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final long value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code long} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final long value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} an {@code int} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final int value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} an {@code int} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final int value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code short} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final short value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code short} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final short value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code byte} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final byte value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code byte} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code char} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final char value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code char} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code double} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final double value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code double} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final double value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code float} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final float value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code float} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final float value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} a {@code boolean} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param value the value to add to the {@code toString} - */ - public void append(final StringBuffer buffer, final String fieldName, final boolean value) { - appendFieldStart(buffer, fieldName); - appendDetail(buffer, fieldName, value); - appendFieldEnd(buffer, fieldName); - } - - /** - * Appends to the {@code toString} a {@code boolean} - * value. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param value the value to add to the {@code toString} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean value) { - buffer.append(value); - } - - /** - * Appends to the {@code toString} an {@link Object} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the toString - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final Object[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of an - * {@link Object} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { - buffer.append(arrayStart); - for (int i = 0; i < array.length; i++) { - appendDetail(buffer, fieldName, i, array[i]); - } - buffer.append(arrayEnd); - } - - /** - * Appends to the {@code toString} the detail of an - * {@link Object} array item. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param i the array item index to add - * @param item the array item to add - * @since 3.11 - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final int i, final Object item) { - if (i > 0) { - buffer.append(arraySeparator); - } - if (item == null) { - appendNullText(buffer, fieldName); - } else { - appendInternal(buffer, fieldName, item, arrayContentDetail); - } - } - - /** - * Appends to the {@code toString} the detail of an array type. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - * @since 2.0 - */ - protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { - buffer.append(arrayStart); - final int length = Array.getLength(array); - for (int i = 0; i < length; i++) { - appendDetail(buffer, fieldName, i, Array.get(array, i)); - } - buffer.append(arrayEnd); - } - /** * Appends to the {@code toString} a summary of an * {@link Object} array. @@ -940,170 +1946,6 @@ public abstract class ToStringStyle implements Serializable { appendSummarySize(buffer, fieldName, array.length); } - /** - * Appends to the {@code toString} a {@code long} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the {@code toString} - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final long[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code long} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code long} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final long[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} an {@code int} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the {@code toString} - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final int[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of an - * {@code int} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of an - * {@code int} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final int[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} a {@code short} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the {@code toString} - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final short[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code short} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - /** * Appends to the {@code toString} a summary of a * {@code short} array. @@ -1117,394 +1959,6 @@ public abstract class ToStringStyle implements Serializable { appendSummarySize(buffer, fieldName, array.length); } - /** - * Appends to the {@code toString} a {@code byte} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the {@code toString} - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final byte[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code byte} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code byte} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} a {@code char} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the {@code toString} - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final char[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code char} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code char} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final char[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} a {@code double} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the toString - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final double[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code double} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code double} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final double[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} a {@code float} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the toString - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final float[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code float} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code float} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final float[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} a {@code boolean} - * array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - * @param array the array to add to the toString - * @param fullDetail {@code true} for detail, {@code false} - * for summary info, {@code null} for style decides - */ - public void append(final StringBuffer buffer, final String fieldName, final boolean[] array, final 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); - } - - /** - * Appends to the {@code toString} the detail of a - * {@code boolean} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendDetail(final StringBuffer buffer, final String fieldName, final 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); - } - - /** - * Appends to the {@code toString} a summary of a - * {@code boolean} array. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - * @param array the array to add to the {@code toString}, - * not {@code null} - */ - protected void appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array) { - appendSummarySize(buffer, fieldName, array.length); - } - - /** - * Appends to the {@code toString} the class name. - * - * @param buffer the {@link StringBuffer} to populate - * @param object the {@link Object} whose name to output - */ - protected void appendClassName(final StringBuffer buffer, final Object object) { - if (useClassName && object != null) { - register(object); - if (useShortClassName) { - buffer.append(getShortClassName(object.getClass())); - } else { - buffer.append(object.getClass().getName()); - } - } - } - - /** - * Appends the {@link System#identityHashCode(java.lang.Object)}. - * - * @param buffer the {@link StringBuffer} to populate - * @param object the {@link Object} whose id to output - */ - protected void appendIdentityHashCode(final StringBuffer buffer, final Object object) { - if (this.isUseIdentityHashCode() && object != null) { - register(object); - buffer.append('@'); - buffer.append(ObjectUtils.identityHashCodeHex(object)); - } - } - - /** - * Appends to the {@code toString} the content start. - * - * @param buffer the {@link StringBuffer} to populate - */ - protected void appendContentStart(final StringBuffer buffer) { - buffer.append(contentStart); - } - - /** - * Appends to the {@code toString} the content end. - * - * @param buffer the {@link StringBuffer} to populate - */ - protected void appendContentEnd(final StringBuffer buffer) { - buffer.append(contentEnd); - } - - /** - * Appends to the {@code toString} an indicator for {@code null}. - * - *

The default indicator is {@code '<null>'}.

- * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - */ - protected void appendNullText(final StringBuffer buffer, final String fieldName) { - buffer.append(nullText); - } - - /** - * Appends to the {@code toString} the field separator. - * - * @param buffer the {@link StringBuffer} to populate - */ - protected void appendFieldSeparator(final StringBuffer buffer) { - buffer.append(fieldSeparator); - } - - /** - * Appends to the {@code toString} the field start. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name - */ - protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { - if (useFieldNames && fieldName != null) { - buffer.append(fieldName); - buffer.append(fieldNameValueSeparator); - } - } - - /** - * Appends to the {@code toString} the field end. - * - * @param buffer the {@link StringBuffer} to populate - * @param fieldName the field name, typically not used as already appended - */ - protected void appendFieldEnd(final StringBuffer buffer, final String fieldName) { - appendFieldSeparator(buffer); - } - /** * Appends to the {@code toString} a size summary. * @@ -1526,6 +1980,218 @@ public abstract class ToStringStyle implements Serializable { buffer.append(sizeEndText); } + /** + * Appends to the {@code toString} the superclass toString. + *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

+ * + *

A {@code null} {@code superToString} is ignored.

+ * + * @param buffer the {@link StringBuffer} to populate + * @param superToString the {@code super.toString()} + * @since 2.0 + */ + public void appendSuper(final StringBuffer buffer, final String superToString) { + appendToString(buffer, superToString); + } + + /** + * Appends to the {@code toString} another toString. + *

NOTE: It assumes that the toString has been created from the same ToStringStyle.

+ * + *

A {@code null} {@code toString} is ignored.

+ * + * @param buffer the {@link StringBuffer} to populate + * @param toString the additional {@code toString} + * @since 2.0 + */ + public void appendToString(final StringBuffer buffer, final String toString) { + if (toString != null) { + final int pos1 = toString.indexOf(contentStart) + contentStart.length(); + final int pos2 = toString.lastIndexOf(contentEnd); + if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) { + if (fieldSeparatorAtStart) { + removeLastFieldSeparator(buffer); + } + buffer.append(toString, pos1, pos2); + appendFieldSeparator(buffer); + } + } + } + + /** + * Gets the array end text. + * + * @return the current array end text + */ + protected String getArrayEnd() { + return arrayEnd; + } + + /** + * Gets the array separator text. + * + * @return the current array separator text + */ + protected String getArraySeparator() { + return arraySeparator; + } + + /** + * Gets the array start text. + * + * @return the current array start text + */ + protected String getArrayStart() { + return arrayStart; + } + + /** + * Gets the content end text. + * + * @return the current content end text + */ + protected String getContentEnd() { + return contentEnd; + } + + /** + * Gets the content start text. + * + * @return the current content start text + */ + protected String getContentStart() { + return contentStart; + } + + /** + * Gets the field name value separator text. + * + * @return the current field name value separator text + */ + protected String getFieldNameValueSeparator() { + return fieldNameValueSeparator; + } + + /** + * Gets the field separator text. + * + * @return the current field separator text + */ + protected String getFieldSeparator() { + return fieldSeparator; + } + + /** + * Gets the text to output when {@code null} found. + * + * @return the current text to output when null found + */ + protected String getNullText() { + return nullText; + } + + /** + * Gets the short class name for a class. + * + *

The short class name is the class name excluding + * the package name.

+ * + * @param cls the {@link Class} to get the short name of + * @return the short name + */ + protected String getShortClassName(final Class cls) { + return ClassUtils.getShortClassName(cls); + } + + /** + * Gets the end text to output when a {@link Collection}, + * {@link 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; + } + + /** + * Gets the start text to output when a {@link Collection}, + * {@link 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; + } + + /** + * Gets the end text to output when an {@link 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; + } + + /** + * Gets the start text to output when an {@link 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; + } + + /** + * Gets whether to output array content detail. + * + * @return the current array content detail setting + */ + protected boolean isArrayContentDetail() { + return arrayContentDetail; + } + + /** + * Gets whether to use full detail when the caller doesn't + * specify. + * + * @return the current defaultFullDetail flag + */ + protected boolean isDefaultFullDetail() { + return defaultFullDetail; + } + + /** + * 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; + } + + /** + * 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; + } + /** * Is this field to be output in full detail. * @@ -1547,19 +2213,6 @@ public abstract class ToStringStyle implements Serializable { return fullDetailRequest.booleanValue(); } - /** - * Gets the short class name for a class. - * - *

The short class name is the class name excluding - * the package name.

- * - * @param cls the {@link Class} to get the short name of - * @return the short name - */ - protected String getShortClassName(final 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) @@ -1573,12 +2226,21 @@ public abstract class ToStringStyle implements Serializable { } /** - * Sets whether to use the class name. + * Gets whether to use the field names passed in. * - * @param useClassName the new useClassName flag + * @return the current useFieldNames flag */ - protected void setUseClassName(final boolean useClassName) { - this.useClassName = useClassName; + protected boolean isUseFieldNames() { + return useFieldNames; + } + + /** + * Gets whether to use the identity hash code. + * + * @return the current useIdentityHashCode flag + */ + protected boolean isUseIdentityHashCode() { + return useIdentityHashCode; } /** @@ -1592,78 +2254,33 @@ public abstract class ToStringStyle implements Serializable { } /** - * Sets whether to output short or long class names. + * Appends to the {@code toString} the detail of an array type. * - * @param useShortClassName the new useShortClassName flag + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} * @since 2.0 */ - protected void setUseShortClassName(final boolean useShortClassName) { - this.useShortClassName = useShortClassName; + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + buffer.append(arrayStart); + final int length = Array.getLength(array); + for (int i = 0; i < length; i++) { + appendDetail(buffer, fieldName, i, Array.get(array, i)); + } + buffer.append(arrayEnd); } /** - * Gets whether to use the identity hash code. + * Remove the last field separator from the buffer. * - * @return the current useIdentityHashCode flag + * @param buffer the {@link StringBuffer} to populate + * @since 2.0 */ - protected boolean isUseIdentityHashCode() { - return useIdentityHashCode; - } - - /** - * Sets whether to use the identity hash code. - * - * @param useIdentityHashCode the new useIdentityHashCode flag - */ - protected void setUseIdentityHashCode(final 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(final 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(final boolean defaultFullDetail) { - this.defaultFullDetail = defaultFullDetail; - } - - /** - * Gets whether to output array content detail. - * - * @return the current array content detail setting - */ - protected boolean isArrayContentDetail() { - return arrayContentDetail; + protected void removeLastFieldSeparator(final StringBuffer buffer) { + if (StringUtils.endsWith(buffer, fieldSeparator)) { + buffer.setLength(buffer.length() - fieldSeparator.length()); + } } /** @@ -1675,39 +2292,6 @@ public abstract class ToStringStyle implements Serializable { this.arrayContentDetail = arrayContentDetail; } - /** - * Gets the array start text. - * - * @return the current array start text - */ - protected String getArrayStart() { - return arrayStart; - } - - /** - * Sets the array start text. - * - *

{@code 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 = StringUtils.EMPTY; - } - this.arrayStart = arrayStart; - } - - /** - * Gets the array end text. - * - * @return the current array end text - */ - protected String getArrayEnd() { - return arrayEnd; - } - /** * Sets the array end text. * @@ -1723,15 +2307,6 @@ public abstract class ToStringStyle implements Serializable { this.arrayEnd = arrayEnd; } - /** - * Gets the array separator text. - * - * @return the current array separator text - */ - protected String getArraySeparator() { - return arraySeparator; - } - /** * Sets the array separator text. * @@ -1748,36 +2323,18 @@ public abstract class ToStringStyle implements Serializable { } /** - * Gets the content start text. - * - * @return the current content start text - */ - protected String getContentStart() { - return contentStart; - } - - /** - * Sets the content start text. + * Sets the array start text. * *

{@code null} is accepted, but will be converted to * an empty String.

* - * @param contentStart the new content start text + * @param arrayStart the new array start text */ - protected void setContentStart(String contentStart) { - if (contentStart == null) { - contentStart = StringUtils.EMPTY; + protected void setArrayStart(String arrayStart) { + if (arrayStart == null) { + arrayStart = StringUtils.EMPTY; } - this.contentStart = contentStart; - } - - /** - * Gets the content end text. - * - * @return the current content end text - */ - protected String getContentEnd() { - return contentEnd; + this.arrayStart = arrayStart; } /** @@ -1796,12 +2353,28 @@ public abstract class ToStringStyle implements Serializable { } /** - * Gets the field name value separator text. + * Sets the content start text. * - * @return the current field name value separator text + *

{@code null} is accepted, but will be converted to + * an empty String.

+ * + * @param contentStart the new content start text */ - protected String getFieldNameValueSeparator() { - return fieldNameValueSeparator; + protected void setContentStart(String contentStart) { + if (contentStart == null) { + contentStart = StringUtils.EMPTY; + } + this.contentStart = contentStart; + } + + /** + * Sets whether to use full detail when the caller doesn't + * specify. + * + * @param defaultFullDetail the new defaultFullDetail flag + */ + protected void setDefaultFullDetail(final boolean defaultFullDetail) { + this.defaultFullDetail = defaultFullDetail; } /** @@ -1819,15 +2392,6 @@ public abstract class ToStringStyle implements Serializable { this.fieldNameValueSeparator = fieldNameValueSeparator; } - /** - * Gets the field separator text. - * - * @return the current field separator text - */ - protected String getFieldSeparator() { - return fieldSeparator; - } - /** * Sets the field separator text. * @@ -1843,39 +2407,6 @@ public abstract class ToStringStyle implements Serializable { 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(final 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. @@ -1888,12 +2419,14 @@ public abstract class ToStringStyle implements Serializable { } /** - * Gets the text to output when {@code null} found. + * Sets whether the field separator should be added at the start + * of each buffer. * - * @return the current text to output when null found + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 */ - protected String getNullText() { - return nullText; + protected void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { + this.fieldSeparatorAtStart = fieldSeparatorAtStart; } /** @@ -1911,48 +2444,6 @@ public abstract class ToStringStyle implements Serializable { this.nullText = nullText; } - /** - * Gets the start text to output when a {@link Collection}, - * {@link 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 {@link Collection}, - * {@link Map} or array size is output. - * - *

This is output before the size value.

- * - *

{@code 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 = StringUtils.EMPTY; - } - this.sizeStartText = sizeStartText; - } - - /** - * Gets the end text to output when a {@link Collection}, - * {@link 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 {@link Collection}, * {@link Map} or array size is output. @@ -1972,45 +2463,21 @@ public abstract class ToStringStyle implements Serializable { } /** - * Gets the start text to output when an {@link 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 {@link Object} is - * output in summary mode. + * Sets the start text to output when a {@link Collection}, + * {@link Map} or array size is output. * *

This is output before the size value.

* *

{@code null} is accepted, but will be converted to * an empty String.

* - * @param summaryObjectStartText the new start of summary text + * @param sizeStartText the new start of size text */ - protected void setSummaryObjectStartText(String summaryObjectStartText) { - if (summaryObjectStartText == null) { - summaryObjectStartText = StringUtils.EMPTY; + protected void setSizeStartText(String sizeStartText) { + if (sizeStartText == null) { + sizeStartText = StringUtils.EMPTY; } - this.summaryObjectStartText = summaryObjectStartText; - } - - /** - * Gets the end text to output when an {@link 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; + this.sizeStartText = sizeStartText; } /** @@ -2032,524 +2499,57 @@ public abstract class ToStringStyle implements Serializable { } /** - * Default {@link ToStringStyle}. + * Sets the start text to output when an {@link Object} is + * output in summary mode. * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability.

+ *

This is output before the size value.

+ * + *

{@code null} is accepted, but will be converted to + * an empty String.

+ * + * @param summaryObjectStartText the new start of summary text */ - 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() { + protected void setSummaryObjectStartText(String summaryObjectStartText) { + if (summaryObjectStartText == null) { + summaryObjectStartText = StringUtils.EMPTY; } - - /** - * Ensure Singleton after serialization. - * - * @return the singleton - */ - private Object readResolve() { - return DEFAULT_STYLE; - } - + this.summaryObjectStartText = summaryObjectStartText; } /** - * {@link ToStringStyle} that does not print out - * the field names. + * Sets whether to use the class name. * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability. + * @param useClassName the new useClassName flag */ - private static final class NoFieldNameToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - *

Use the static constant rather than instantiating.

- */ - NoFieldNameToStringStyle() { - this.setUseFieldNames(false); - } - - /** - * Ensure Singleton after serialization. - * - * @return the singleton - */ - private Object readResolve() { - return NO_FIELD_NAMES_STYLE; - } - + protected void setUseClassName(final boolean useClassName) { + this.useClassName = useClassName; } /** - * {@link ToStringStyle} that prints out the short - * class name and no identity hash code. + * Sets whether to use the field names passed in. * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability.

+ * @param useFieldNames the new useFieldNames flag */ - private static final class ShortPrefixToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - *

Use the static constant rather than instantiating.

- */ - ShortPrefixToStringStyle() { - this.setUseShortClassName(true); - this.setUseIdentityHashCode(false); - } - - /** - * Ensure Singleton after serialization. - * @return the singleton - */ - private Object readResolve() { - return SHORT_PREFIX_STYLE; - } - + protected void setUseFieldNames(final boolean useFieldNames) { + this.useFieldNames = useFieldNames; } /** - * {@link ToStringStyle} that does not print out the - * class name, identity hash code, content start or field name. + * Sets whether to use the identity hash code. * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability.

+ * @param useIdentityHashCode the new useIdentityHashCode flag */ - private static final class SimpleToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - *

Use the static constant rather than instantiating.

- */ - SimpleToStringStyle() { - this.setUseClassName(false); - this.setUseIdentityHashCode(false); - this.setUseFieldNames(false); - this.setContentStart(StringUtils.EMPTY); - this.setContentEnd(StringUtils.EMPTY); - } - - /** - * Ensure Singleton after serialization. - * @return the singleton - */ - private Object readResolve() { - return SIMPLE_STYLE; - } - + protected void setUseIdentityHashCode(final boolean useIdentityHashCode) { + this.useIdentityHashCode = useIdentityHashCode; } /** - * {@link ToStringStyle} that outputs on multiple lines. + * Sets whether to output short or long class names. * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability.

+ * @param useShortClassName the new useShortClassName flag + * @since 2.0 */ - private static final class MultiLineToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - *

Use the static constant rather than instantiating.

- */ - MultiLineToStringStyle() { - this.setContentStart("["); - this.setFieldSeparator(System.lineSeparator() + " "); - this.setFieldSeparatorAtStart(true); - this.setContentEnd(System.lineSeparator() + "]"); - } - - /** - * Ensure Singleton after serialization. - * - * @return the singleton - */ - private Object readResolve() { - return MULTI_LINE_STYLE; - } - - } - - /** - * {@link ToStringStyle} that does not print out the class name - * and identity hash code but prints content start and field names. - * - *

This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability.

- */ - private static final class NoClassNameToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - /** - * Constructor. - * - *

Use the static constant rather than instantiating.

- */ - NoClassNameToStringStyle() { - this.setUseClassName(false); - this.setUseIdentityHashCode(false); - } - - /** - * Ensure Singleton after serialization. - * - * @return the singleton - */ - private Object readResolve() { - return NO_CLASS_NAME_STYLE; - } - - } - - /** - * {@link ToStringStyle} that outputs with JSON format. - * - *

- * This is an inner class rather than using - * {@link StandardToStringStyle} to ensure its immutability. - *

- * - * @since 3.4 - * @see json.org - */ - private static final class JsonToStringStyle extends ToStringStyle { - - private static final long serialVersionUID = 1L; - - private static final String FIELD_NAME_QUOTE = "\""; - - /** - * Constructor. - * - *

- * Use the static constant rather than instantiating. - *

- */ - JsonToStringStyle() { - this.setUseClassName(false); - this.setUseIdentityHashCode(false); - - this.setContentStart("{"); - this.setContentEnd("}"); - - this.setArrayStart("["); - this.setArrayEnd("]"); - - this.setFieldSeparator(","); - this.setFieldNameValueSeparator(":"); - - this.setNullText("null"); - - this.setSummaryObjectStartText("\"<"); - this.setSummaryObjectEndText(">\""); - - this.setSizeStartText("\"\""); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, - final Object[] array, final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, final long[] array, - final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, final int[] array, - final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, - final short[] array, final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, final byte[] array, - final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, final char[] array, - final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, - final double[] array, final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, - final float[] array, final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, - final boolean[] array, final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, array, fullDetail); - } - - @Override - public void append(final StringBuffer buffer, final String fieldName, final Object value, - final Boolean fullDetail) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - if (!isFullDetail(fullDetail)) { - throw new UnsupportedOperationException( - "FullDetail must be true when using JsonToStringStyle"); - } - - super.append(buffer, fieldName, value, fullDetail); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { - appendValueAsString(buffer, String.valueOf(value)); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { - - if (value == null) { - appendNullText(buffer, fieldName); - return; - } - - if (value instanceof String || value instanceof Character) { - appendValueAsString(buffer, value.toString()); - return; - } - - if (value instanceof Number || value instanceof Boolean) { - buffer.append(value); - return; - } - - final String valueAsString = value.toString(); - if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) { - buffer.append(value); - return; - } - - appendDetail(buffer, fieldName, valueAsString); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection coll) { - if (coll != null && !coll.isEmpty()) { - buffer.append(getArrayStart()); - int i = 0; - for (final Object item : coll) { - appendDetail(buffer, fieldName, i++, item); - } - buffer.append(getArrayEnd()); - return; - } - - buffer.append(coll); - } - - @Override - protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map map) { - if (map != null && !map.isEmpty()) { - buffer.append(getContentStart()); - - boolean firstItem = true; - for (final Entry entry : map.entrySet()) { - final String keyStr = Objects.toString(entry.getKey(), null); - if (keyStr != null) { - if (firstItem) { - firstItem = false; - } else { - appendFieldEnd(buffer, keyStr); - } - appendFieldStart(buffer, keyStr); - final Object value = entry.getValue(); - if (value == null) { - appendNullText(buffer, keyStr); - } else { - appendInternal(buffer, keyStr, value, true); - } - } - } - - buffer.append(getContentEnd()); - return; - } - - buffer.append(map); - } - - private boolean isJsonArray(final String valueAsString) { - return valueAsString.startsWith(getArrayStart()) - && valueAsString.endsWith(getArrayEnd()); - } - - private boolean isJsonObject(final String valueAsString) { - return valueAsString.startsWith(getContentStart()) - && valueAsString.endsWith(getContentEnd()); - } - - /** - * Appends the given String enclosed in double-quotes to the given StringBuffer. - * - * @param buffer the StringBuffer to append the value to. - * @param value the value to append. - */ - private void appendValueAsString(final StringBuffer buffer, final String value) { - buffer.append('"').append(StringEscapeUtils.escapeJson(value)).append('"'); - } - - @Override - protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { - - if (fieldName == null) { - throw new UnsupportedOperationException( - "Field names are mandatory when using JsonToStringStyle"); - } - - super.appendFieldStart(buffer, FIELD_NAME_QUOTE + StringEscapeUtils.escapeJson(fieldName) - + FIELD_NAME_QUOTE); - } - - /** - * Ensure Singleton after serialization. - * - * @return the singleton - */ - private Object readResolve() { - return JSON_STYLE; - } - + protected void setUseShortClassName(final boolean useShortClassName) { + this.useShortClassName = useShortClassName; } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java index 27c530245..1d6762928 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java @@ -28,111 +28,6 @@ import java.util.concurrent.atomic.AtomicReference; */ public abstract class AbstractCircuitBreaker implements CircuitBreaker { - /** - * The name of the open property as it is passed to registered - * change listeners. - */ - public static final String PROPERTY_NAME = "open"; - - /** The current state of this circuit breaker. */ - protected final AtomicReference state = new AtomicReference<>(State.CLOSED); - - /** An object for managing change listeners registered at this instance. */ - private final PropertyChangeSupport changeSupport; - - /** - * Creates an {@link AbstractCircuitBreaker}. It also creates an internal {@link PropertyChangeSupport}. - */ - public AbstractCircuitBreaker() { - changeSupport = new PropertyChangeSupport(this); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isOpen() { - return isOpen(state.get()); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isClosed() { - return !isOpen(); - } - - /** - * {@inheritDoc} - */ - @Override - public abstract boolean checkState(); - - /** - * {@inheritDoc} - */ - @Override - public abstract boolean incrementAndCheckState(T increment); - - /** - * {@inheritDoc} - */ - @Override - public void close() { - changeState(State.CLOSED); - } - - /** - * {@inheritDoc} - */ - @Override - public void open() { - changeState(State.OPEN); - } - - /** - * Converts the given state value to a boolean open property. - * - * @param state the state to be converted - * @return the boolean open flag - */ - protected static boolean isOpen(final State state) { - return state == State.OPEN; - } - - /** - * Changes the internal state of this circuit breaker. If there is actually a change - * of the state value, all registered change listeners are notified. - * - * @param newState the new state to be set - */ - protected void changeState(final State newState) { - if (state.compareAndSet(newState.oppositeState(), newState)) { - changeSupport.firePropertyChange(PROPERTY_NAME, !isOpen(newState), isOpen(newState)); - } - } - - /** - * Adds a change listener to this circuit breaker. This listener is notified whenever - * the state of this circuit breaker changes. If the listener is - * null, it is silently ignored. - * - * @param listener the listener to be added - */ - public void addChangeListener(final PropertyChangeListener listener) { - changeSupport.addPropertyChangeListener(listener); - } - - /** - * Removes the specified change listener from this circuit breaker. - * - * @param listener the listener to be removed - */ - public void removeChangeListener(final PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(listener); - } - /** * An internal enumeration representing the different states of a circuit * breaker. This class also contains some logic for performing state @@ -172,4 +67,109 @@ public abstract class AbstractCircuitBreaker implements CircuitBreaker { public abstract State oppositeState(); } + /** + * The name of the open property as it is passed to registered + * change listeners. + */ + public static final String PROPERTY_NAME = "open"; + + /** + * Converts the given state value to a boolean open property. + * + * @param state the state to be converted + * @return the boolean open flag + */ + protected static boolean isOpen(final State state) { + return state == State.OPEN; + } + + /** The current state of this circuit breaker. */ + protected final AtomicReference state = new AtomicReference<>(State.CLOSED); + + /** An object for managing change listeners registered at this instance. */ + private final PropertyChangeSupport changeSupport; + + /** + * Creates an {@link AbstractCircuitBreaker}. It also creates an internal {@link PropertyChangeSupport}. + */ + public AbstractCircuitBreaker() { + changeSupport = new PropertyChangeSupport(this); + } + + /** + * Adds a change listener to this circuit breaker. This listener is notified whenever + * the state of this circuit breaker changes. If the listener is + * null, it is silently ignored. + * + * @param listener the listener to be added + */ + public void addChangeListener(final PropertyChangeListener listener) { + changeSupport.addPropertyChangeListener(listener); + } + + /** + * Changes the internal state of this circuit breaker. If there is actually a change + * of the state value, all registered change listeners are notified. + * + * @param newState the new state to be set + */ + protected void changeState(final State newState) { + if (state.compareAndSet(newState.oppositeState(), newState)) { + changeSupport.firePropertyChange(PROPERTY_NAME, !isOpen(newState), isOpen(newState)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public abstract boolean checkState(); + + /** + * {@inheritDoc} + */ + @Override + public void close() { + changeState(State.CLOSED); + } + + /** + * {@inheritDoc} + */ + @Override + public abstract boolean incrementAndCheckState(T increment); + + /** + * {@inheritDoc} + */ + @Override + public boolean isClosed() { + return !isOpen(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOpen() { + return isOpen(state.get()); + } + + /** + * {@inheritDoc} + */ + @Override + public void open() { + changeState(State.OPEN); + } + + /** + * Removes the specified change listener from this circuit breaker. + * + * @param listener the listener to be removed + */ + public void removeChangeListener(final PropertyChangeListener listener) { + changeSupport.removePropertyChangeListener(listener); + } + } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java index fb61e778f..003ea0ad7 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java @@ -147,6 +147,14 @@ public abstract class AbstractConcurrentInitializer impl } } + /** + * Gets an Exception with a type of E as defined by a concrete subclass of this class. + * + * @param e The actual exception that was thrown + * @return a new exception with the actual type of E, that wraps e. + */ + protected abstract E getTypedException(Exception e); + /** * Creates and initializes the object managed by this {@code * ConcurrentInitializer}. This method is called by {@link #get()} when the object is accessed for the first time. An implementation can focus on the @@ -187,12 +195,4 @@ public abstract class AbstractConcurrentInitializer impl */ protected abstract boolean isInitialized(); - /** - * Gets an Exception with a type of E as defined by a concrete subclass of this class. - * - * @param e The actual exception that was thrown - * @return a new exception with the actual type of E, that wraps e. - */ - protected abstract E getTypedException(Exception e); - } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java index affd8c81d..c02960f90 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java @@ -86,9 +86,6 @@ public class AtomicInitializer extends AbstractConcurrentInitializer reference = new AtomicReference<>(getNoInit()); - /** * Creates a new builder. * @@ -100,6 +97,9 @@ public class AtomicInitializer extends AbstractConcurrentInitializer(); } + /** Holds the reference to the managed object. */ + private final AtomicReference reference = new AtomicReference<>(getNoInit()); + /** * Constructs a new instance. */ @@ -147,6 +147,14 @@ public class AtomicInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer> factory = new AtomicReference<>(); - - /** Holds the reference to the managed object. */ - private final AtomicReference reference = new AtomicReference<>(getNoInit()); - /** * Creates a new builder. * @@ -92,6 +86,12 @@ public class AtomicSafeInitializer extends AbstractConcurrentInitializer(); } + /** A guard which ensures that initialize() is called only once. */ + private final AtomicReference> factory = new AtomicReference<>(); + + /** Holds the reference to the managed object. */ + private final AtomicReference reference = new AtomicReference<>(getNoInit()); + /** * Constructs a new instance. */ @@ -135,6 +135,14 @@ public class AtomicSafeInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer { + /** Stores the executor service to be destroyed at the end. */ + private final ExecutorService execFinally; + + /** + * Creates a new instance of {@link InitializationTask} and initializes + * it with the {@link ExecutorService} to be destroyed at the end. + * + * @param exec the {@link ExecutorService} + */ + InitializationTask(final ExecutorService exec) { + execFinally = exec; } + /** + * Initiates initialization and returns the result. + * + * @return the result object + * @throws Exception if an error occurs + */ + @Override + public T call() throws Exception { + try { + return initialize(); + } finally { + if (execFinally != null) { + execFinally.shutdown(); + } + } + } + } + + /** + * Creates a new builder. + * + * @param the type of object to build. + * @return a new builder. + * @since 3.14.0 + */ + public static Builder, T> builder() { + return new Builder<>(); } /** The external executor service for executing tasks. */ @@ -153,17 +196,6 @@ public class BackgroundInitializer extends AbstractConcurrentInitializer the type of object to build. - * @return a new builder. - * @since 3.14.0 - */ - public static Builder, T> builder() { - return new Builder<>(); - } - /** * Constructs a new instance. * @@ -176,6 +208,72 @@ public class BackgroundInitializer extends AbstractConcurrentInitializer createTask(final ExecutorService execDestroy) { + return new InitializationTask(execDestroy); + } + + /** + * Returns the result of the background initialization. This method blocks + * until initialization is complete. If the background processing caused a + * runtime exception, it is directly thrown by this method. Checked + * exceptions, including {@link InterruptedException} are wrapped in a + * {@link ConcurrentException}. Calling this method before {@link #start()} + * was called causes an {@link IllegalStateException} exception to be + * thrown. + * + * @return the object produced by this initializer + * @throws ConcurrentException if a checked exception occurred during + * background processing + * @throws IllegalStateException if {@link #start()} has not been called + */ + @Override + public T get() throws ConcurrentException { + try { + return getFuture().get(); + } catch (final ExecutionException execex) { + ConcurrentUtils.handleCause(execex); + return null; // should not be reached + } catch (final InterruptedException iex) { + // reset interrupted state + Thread.currentThread().interrupt(); + throw new ConcurrentException(iex); + } + } + + /** + * Returns the {@link ExecutorService} that is actually used for executing + * the background task. This method can be called after {@link #start()} + * (before {@code start()} it returns null). If an external executor + * was set, this is also the active executor. Otherwise this method returns + * the temporary executor that was created by this object. + * + * @return the {@link ExecutorService} for executing the background task + */ + protected final synchronized ExecutorService getActiveExecutor() { + return executor; + } + /** * Returns the external {@link ExecutorService} to be used by this class. * @@ -185,6 +283,46 @@ public class BackgroundInitializer extends AbstractConcurrentInitializer getFuture() { + if (future == null) { + throw new IllegalStateException("start() must be called first!"); + } + + return future; + } + + /** + * Returns the number of background tasks to be created for this + * initializer. This information is evaluated when a temporary {@code + * ExecutorService} is created. This base implementation returns 1. Derived + * classes that do more complex background processing can override it. This + * method is called from a synchronized block by the {@link #start()} + * method. Therefore overriding methods should be careful with obtaining + * other locks and return as fast as possible. + * + * @return the number of background tasks required by this initializer + */ + protected int getTaskCount() { + return 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected Exception getTypedException(Exception e) { + //This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown + return new Exception(e); + } + /** * Tests whether this instance is initialized. Once initialized, always returns true. * If initialization failed then the failure will be cached and this will never return @@ -273,142 +411,4 @@ public class BackgroundInitializer extends AbstractConcurrentInitializer getFuture() { - if (future == null) { - throw new IllegalStateException("start() must be called first!"); - } - - return future; - } - - /** - * Returns the {@link ExecutorService} that is actually used for executing - * the background task. This method can be called after {@link #start()} - * (before {@code start()} it returns null). If an external executor - * was set, this is also the active executor. Otherwise this method returns - * the temporary executor that was created by this object. - * - * @return the {@link ExecutorService} for executing the background task - */ - protected final synchronized ExecutorService getActiveExecutor() { - return executor; - } - - /** - * Returns the number of background tasks to be created for this - * initializer. This information is evaluated when a temporary {@code - * ExecutorService} is created. This base implementation returns 1. Derived - * classes that do more complex background processing can override it. This - * method is called from a synchronized block by the {@link #start()} - * method. Therefore overriding methods should be careful with obtaining - * other locks and return as fast as possible. - * - * @return the number of background tasks required by this initializer - */ - protected int getTaskCount() { - return 1; - } - - /** - * Creates a task for the background initialization. The {@link Callable} - * object returned by this method is passed to the {@link ExecutorService}. - * This implementation returns a task that invokes the {@link #initialize()} - * method. If a temporary {@link ExecutorService} is used, it is destroyed - * at the end of the task. - * - * @param execDestroy the {@link ExecutorService} to be destroyed by the - * task - * @return a task for the background initialization - */ - private Callable createTask(final ExecutorService execDestroy) { - return new InitializationTask(execDestroy); - } - - /** - * Creates the {@link ExecutorService} to be used. This method is called if - * no {@link ExecutorService} was provided at construction time. - * - * @return the {@link ExecutorService} to be used - */ - private ExecutorService createExecutor() { - return Executors.newFixedThreadPool(getTaskCount()); - } - - private class InitializationTask implements Callable { - /** Stores the executor service to be destroyed at the end. */ - private final ExecutorService execFinally; - - /** - * Creates a new instance of {@link InitializationTask} and initializes - * it with the {@link ExecutorService} to be destroyed at the end. - * - * @param exec the {@link ExecutorService} - */ - InitializationTask(final ExecutorService exec) { - execFinally = exec; - } - - /** - * Initiates initialization and returns the result. - * - * @return the result object - * @throws Exception if an error occurs - */ - @Override - public T call() throws Exception { - try { - return initialize(); - } finally { - if (execFinally != null) { - execFinally.shutdown(); - } - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected Exception getTypedException(Exception e) { - //This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown - return new Exception(e); - } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java b/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java index 20a6ed80a..d9ba392b6 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java @@ -89,6 +89,140 @@ import java.util.concurrent.atomic.AtomicLong; * @since 3.0 */ public class BasicThreadFactory implements ThreadFactory { + /** + * A builder class for creating instances of {@code + * BasicThreadFactory}. + * + *

+ * Using this builder class instances of {@link BasicThreadFactory} can be + * created and initialized. The class provides methods that correspond to + * the configuration options supported by {@link BasicThreadFactory}. Method + * chaining is supported. Refer to the documentation of {@code + * BasicThreadFactory} for a usage example. + *

+ * + */ + public static class Builder + implements org.apache.commons.lang3.builder.Builder { + + /** The wrapped factory. */ + private ThreadFactory wrappedFactory; + + /** The uncaught exception handler. */ + private Thread.UncaughtExceptionHandler exceptionHandler; + + /** The naming pattern. */ + private String namingPattern; + + /** The priority. */ + private Integer priority; + + /** The daemon flag. */ + private Boolean daemon; + + /** + * Creates a new {@link BasicThreadFactory} with all configuration + * options that have been specified by calling methods on this builder. + * After creating the factory {@link #reset()} is called. + * + * @return the new {@link BasicThreadFactory} + */ + @Override + public BasicThreadFactory build() { + final BasicThreadFactory factory = new BasicThreadFactory(this); + reset(); + return factory; + } + + /** + * Sets the daemon flag for the new {@link BasicThreadFactory}. If this + * flag is set to true the new thread factory will create daemon + * threads. + * + * @param daemon the value of the daemon flag + * @return a reference to this {@link Builder} + */ + public Builder daemon(final boolean daemon) { + this.daemon = Boolean.valueOf(daemon); + return this; + } + + /** + * Sets the naming pattern to be used by the new {@code + * BasicThreadFactory}. + * + * @param pattern the naming pattern (must not be null) + * @return a reference to this {@link Builder} + * @throws NullPointerException if the naming pattern is null + */ + public Builder namingPattern(final String pattern) { + Objects.requireNonNull(pattern, "pattern"); + + namingPattern = pattern; + return this; + } + + /** + * Sets the priority for the threads created by the new {@code + * BasicThreadFactory}. + * + * @param priority the priority + * @return a reference to this {@link Builder} + */ + public Builder priority(final int priority) { + this.priority = Integer.valueOf(priority); + return this; + } + + /** + * Resets this builder. All configuration options are set to default + * values. Note: If the {@link #build()} method was called, it is not + * necessary to call {@code reset()} explicitly because this is done + * automatically. + */ + public void reset() { + wrappedFactory = null; + exceptionHandler = null; + namingPattern = null; + priority = null; + daemon = null; + } + + /** + * Sets the uncaught exception handler for the threads created by the + * new {@link BasicThreadFactory}. + * + * @param handler the {@link UncaughtExceptionHandler} (must not be + * null) + * @return a reference to this {@link Builder} + * @throws NullPointerException if the exception handler is null + */ + public Builder uncaughtExceptionHandler( + final Thread.UncaughtExceptionHandler handler) { + Objects.requireNonNull(handler, "handler"); + + exceptionHandler = handler; + return this; + } + + /** + * Sets the {@link ThreadFactory} to be wrapped by the new {@code + * BasicThreadFactory}. + * + * @param factory the wrapped {@link ThreadFactory} (must not be + * null) + * @return a reference to this {@link Builder} + * @throws NullPointerException if the passed in {@link ThreadFactory} + * is null + */ + public Builder wrappedFactory(final ThreadFactory factory) { + Objects.requireNonNull(factory, "factory"); + + wrappedFactory = factory; + return this; + } + } + /** A counter for the threads created by this factory. */ private final AtomicLong threadCounter; @@ -129,15 +263,15 @@ public class BasicThreadFactory implements ThreadFactory { } /** - * Returns the wrapped {@link ThreadFactory}. This factory is used for - * actually creating threads. This method never returns null. If no - * {@link ThreadFactory} was passed when this object was created, a default - * thread factory is returned. + * Returns the daemon flag. This flag determines whether newly created + * threads should be daemon threads. If true, this factory object + * calls {@code setDaemon(true)} on the newly created threads. Result can be + * null if no daemon flag was provided at creation time. * - * @return the wrapped {@link ThreadFactory} + * @return the daemon flag */ - public final ThreadFactory getWrappedFactory() { - return wrappedFactory; + public final Boolean getDaemonFlag() { + return daemon; } /** @@ -150,18 +284,6 @@ public class BasicThreadFactory implements ThreadFactory { return namingPattern; } - /** - * Returns the daemon flag. This flag determines whether newly created - * threads should be daemon threads. If true, this factory object - * calls {@code setDaemon(true)} on the newly created threads. Result can be - * null if no daemon flag was provided at creation time. - * - * @return the daemon flag - */ - public final Boolean getDaemonFlag() { - return daemon; - } - /** * Returns the priority of the threads created by this factory. Result can * be null if no priority was specified. @@ -172,16 +294,6 @@ public class BasicThreadFactory implements ThreadFactory { return priority; } - /** - * Returns the {@link UncaughtExceptionHandler} for the threads created by - * this factory. Result can be null if no handler was provided. - * - * @return the {@link UncaughtExceptionHandler} - */ - public final Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { - return uncaughtExceptionHandler; - } - /** * Returns the number of threads this factory has already created. This * class maintains an internal counter that is incremented each time the @@ -194,19 +306,25 @@ public class BasicThreadFactory implements ThreadFactory { } /** - * Creates a new thread. This implementation delegates to the wrapped - * factory for creating the thread. Then, on the newly created thread the - * corresponding configuration options are set. + * Returns the {@link UncaughtExceptionHandler} for the threads created by + * this factory. Result can be null if no handler was provided. * - * @param runnable the {@link Runnable} to be executed by the new thread - * @return the newly created thread + * @return the {@link UncaughtExceptionHandler} */ - @Override - public Thread newThread(final Runnable runnable) { - final Thread thread = getWrappedFactory().newThread(runnable); - initializeThread(thread); + public final Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler; + } - return thread; + /** + * Returns the wrapped {@link ThreadFactory}. This factory is used for + * actually creating threads. This method never returns null. If no + * {@link ThreadFactory} was passed when this object was created, a default + * thread factory is returned. + * + * @return the wrapped {@link ThreadFactory} + */ + public final ThreadFactory getWrappedFactory() { + return wrappedFactory; } /** @@ -238,136 +356,18 @@ public class BasicThreadFactory implements ThreadFactory { } /** - * A builder class for creating instances of {@code - * BasicThreadFactory}. - * - *

- * Using this builder class instances of {@link BasicThreadFactory} can be - * created and initialized. The class provides methods that correspond to - * the configuration options supported by {@link BasicThreadFactory}. Method - * chaining is supported. Refer to the documentation of {@code - * BasicThreadFactory} for a usage example. - *

+ * Creates a new thread. This implementation delegates to the wrapped + * factory for creating the thread. Then, on the newly created thread the + * corresponding configuration options are set. * + * @param runnable the {@link Runnable} to be executed by the new thread + * @return the newly created thread */ - public static class Builder - implements org.apache.commons.lang3.builder.Builder { + @Override + public Thread newThread(final Runnable runnable) { + final Thread thread = getWrappedFactory().newThread(runnable); + initializeThread(thread); - /** The wrapped factory. */ - private ThreadFactory wrappedFactory; - - /** The uncaught exception handler. */ - private Thread.UncaughtExceptionHandler exceptionHandler; - - /** The naming pattern. */ - private String namingPattern; - - /** The priority. */ - private Integer priority; - - /** The daemon flag. */ - private Boolean daemon; - - /** - * Sets the {@link ThreadFactory} to be wrapped by the new {@code - * BasicThreadFactory}. - * - * @param factory the wrapped {@link ThreadFactory} (must not be - * null) - * @return a reference to this {@link Builder} - * @throws NullPointerException if the passed in {@link ThreadFactory} - * is null - */ - public Builder wrappedFactory(final ThreadFactory factory) { - Objects.requireNonNull(factory, "factory"); - - wrappedFactory = factory; - return this; - } - - /** - * Sets the naming pattern to be used by the new {@code - * BasicThreadFactory}. - * - * @param pattern the naming pattern (must not be null) - * @return a reference to this {@link Builder} - * @throws NullPointerException if the naming pattern is null - */ - public Builder namingPattern(final String pattern) { - Objects.requireNonNull(pattern, "pattern"); - - namingPattern = pattern; - return this; - } - - /** - * Sets the daemon flag for the new {@link BasicThreadFactory}. If this - * flag is set to true the new thread factory will create daemon - * threads. - * - * @param daemon the value of the daemon flag - * @return a reference to this {@link Builder} - */ - public Builder daemon(final boolean daemon) { - this.daemon = Boolean.valueOf(daemon); - return this; - } - - /** - * Sets the priority for the threads created by the new {@code - * BasicThreadFactory}. - * - * @param priority the priority - * @return a reference to this {@link Builder} - */ - public Builder priority(final int priority) { - this.priority = Integer.valueOf(priority); - return this; - } - - /** - * Sets the uncaught exception handler for the threads created by the - * new {@link BasicThreadFactory}. - * - * @param handler the {@link UncaughtExceptionHandler} (must not be - * null) - * @return a reference to this {@link Builder} - * @throws NullPointerException if the exception handler is null - */ - public Builder uncaughtExceptionHandler( - final Thread.UncaughtExceptionHandler handler) { - Objects.requireNonNull(handler, "handler"); - - exceptionHandler = handler; - return this; - } - - /** - * Resets this builder. All configuration options are set to default - * values. Note: If the {@link #build()} method was called, it is not - * necessary to call {@code reset()} explicitly because this is done - * automatically. - */ - public void reset() { - wrappedFactory = null; - exceptionHandler = null; - namingPattern = null; - priority = null; - daemon = null; - } - - /** - * Creates a new {@link BasicThreadFactory} with all configuration - * options that have been specified by calling methods on this builder. - * After creating the factory {@link #reset()} is called. - * - * @return the new {@link BasicThreadFactory} - */ - @Override - public BasicThreadFactory build() { - final BasicThreadFactory factory = new BasicThreadFactory(this); - reset(); - return factory; - } + return thread; } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java index 81d6efab0..13a08787f 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java @@ -96,19 +96,6 @@ public class CallableBackgroundInitializer extends BackgroundInitializer { callable = call; } - /** - * Performs initialization in a background thread. This implementation - * delegates to the {@link Callable} passed at construction time of this - * object. - * - * @return the result of the initialization - * @throws Exception if an error occurs - */ - @Override - protected T initialize() throws Exception { - return callable.call(); - } - /** * Tests the passed in {@link Callable} and throws an exception if it is * undefined. @@ -128,4 +115,17 @@ public class CallableBackgroundInitializer extends BackgroundInitializer { //This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown return new Exception(e); } + + /** + * Performs initialization in a background thread. This implementation + * delegates to the {@link Callable} passed at construction time of this + * object. + * + * @return the result of the initialization + * @throws Exception if an error occurs + */ + @Override + protected T initialize() throws Exception { + return callable.call(); + } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java index 164eb92b6..ca6e58a02 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java @@ -40,24 +40,6 @@ package org.apache.commons.lang3.concurrent; */ public interface CircuitBreaker { - /** - * Tests the current open state of this circuit breaker. A return value of - * true means that the circuit breaker is currently open indicating a - * problem in the monitored subsystem. - * - * @return the current open state of this circuit breaker. - */ - boolean isOpen(); - - /** - * Tests the current closed state of this circuit breaker. A return value of - * true means that the circuit breaker is currently closed. This - * means that everything is okay with the monitored subsystem. - * - * @return the current closed state of this circuit breaker. - */ - boolean isClosed(); - /** * Checks the state of this circuit breaker and changes it if necessary. The return * value indicates whether the circuit breaker is now in state closed; a value @@ -74,13 +56,6 @@ public interface CircuitBreaker { */ void close(); - /** - * Opens this circuit breaker. Its state is changed to open. Depending on a concrete - * implementation, it may close itself again if the monitored subsystem becomes - * available. If this circuit breaker is already open, this method has no effect. - */ - void open(); - /** * Increments the monitored value and performs a check of the current state of this * circuit breaker. This method works like {@link #checkState()}, but the monitored @@ -91,4 +66,29 @@ public interface CircuitBreaker { * false otherwise */ boolean incrementAndCheckState(T increment); + + /** + * Tests the current closed state of this circuit breaker. A return value of + * true means that the circuit breaker is currently closed. This + * means that everything is okay with the monitored subsystem. + * + * @return the current closed state of this circuit breaker. + */ + boolean isClosed(); + + /** + * Tests the current open state of this circuit breaker. A return value of + * true means that the circuit breaker is currently open indicating a + * problem in the monitored subsystem. + * + * @return the current open state of this circuit breaker. + */ + boolean isOpen(); + + /** + * Opens this circuit breaker. Its state is changed to open. Depending on a concrete + * implementation, it may close itself again if the monitored subsystem becomes + * available. If this circuit breaker is already open, this method has no effect. + */ + void open(); } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java index de5022782..e81bc5974 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java @@ -34,6 +34,15 @@ public class CircuitBreakingException extends RuntimeException { public CircuitBreakingException() { } + /** + * Creates a new instance of {@link CircuitBreakingException} and initializes it with the given message. + * + * @param message the error message + */ + public CircuitBreakingException(final String message) { + super(message); + } + /** * Creates a new instance of {@link CircuitBreakingException} and initializes it with the given message and cause. * @@ -44,15 +53,6 @@ public class CircuitBreakingException extends RuntimeException { super(message, cause); } - /** - * Creates a new instance of {@link CircuitBreakingException} and initializes it with the given message. - * - * @param message the error message - */ - public CircuitBreakingException(final String message) { - super(message); - } - /** * Creates a new instance of {@link CircuitBreakingException} and initializes it with the given cause. * diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java index 87cc23899..2321b47db 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java @@ -41,17 +41,6 @@ public class ConcurrentException extends Exception { protected ConcurrentException() { } - /** - * Creates a new instance of {@link ConcurrentException} and initializes it - * with the given cause. - * - * @param cause the cause of this exception - * @throws IllegalArgumentException if the cause is not a checked exception - */ - public ConcurrentException(final Throwable cause) { - super(ConcurrentUtils.checkedException(cause)); - } - /** * Creates a new instance of {@link ConcurrentException} and initializes it * with the given message and cause. @@ -63,4 +52,15 @@ public class ConcurrentException extends Exception { public ConcurrentException(final String msg, final Throwable cause) { super(msg, ConcurrentUtils.checkedException(cause)); } + + /** + * Creates a new instance of {@link ConcurrentException} and initializes it + * with the given cause. + * + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentException(final Throwable cause) { + super(ConcurrentUtils.checkedException(cause)); + } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java index 9cf5bda22..9125159ef 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java @@ -44,17 +44,6 @@ public class ConcurrentRuntimeException extends RuntimeException { protected ConcurrentRuntimeException() { } - /** - * Creates a new instance of {@link ConcurrentRuntimeException} and - * initializes it with the given cause. - * - * @param cause the cause of this exception - * @throws IllegalArgumentException if the cause is not a checked exception - */ - public ConcurrentRuntimeException(final Throwable cause) { - super(ConcurrentUtils.checkedException(cause)); - } - /** * Creates a new instance of {@link ConcurrentRuntimeException} and * initializes it with the given message and cause. @@ -66,4 +55,15 @@ public class ConcurrentRuntimeException extends RuntimeException { public ConcurrentRuntimeException(final String msg, final Throwable cause) { super(msg, ConcurrentUtils.checkedException(cause)); } + + /** + * Creates a new instance of {@link ConcurrentRuntimeException} and + * initializes it with the given cause. + * + * @param cause the cause of this exception + * @throws IllegalArgumentException if the cause is not a checked exception + */ + public ConcurrentRuntimeException(final Throwable cause) { + super(ConcurrentUtils.checkedException(cause)); + } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java index f2eeae32d..70cb75083 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java @@ -33,10 +33,156 @@ import org.apache.commons.lang3.exception.ExceptionUtils; public class ConcurrentUtils { /** - * Private constructor so that no instances can be created. This class - * contains only static utility methods. + * A specialized {@link Future} implementation which wraps a constant value. + * @param the type of the value wrapped by this class */ - private ConcurrentUtils() { + static final class ConstantFuture implements Future { + /** The constant value. */ + private final T value; + + /** + * Creates a new instance of {@link ConstantFuture} and initializes it + * with the constant value. + * + * @param value the value (may be null) + */ + ConstantFuture(final T value) { + this.value = value; + } + + /** + * {@inheritDoc} The cancel operation is not supported. This + * implementation always returns false. + */ + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return false; + } + + /** + * {@inheritDoc} This implementation just returns the constant value. + */ + @Override + public T get() { + return value; + } + + /** + * {@inheritDoc} This implementation just returns the constant value; it + * does not block, therefore the timeout has no meaning. + */ + @Override + public T get(final long timeout, final TimeUnit unit) { + return value; + } + + /** + * {@inheritDoc} This implementation always returns false; there + * is no background process which could be cancelled. + */ + @Override + public boolean isCancelled() { + return false; + } + + /** + * {@inheritDoc} This implementation always returns true because + * the constant object managed by this {@link Future} implementation is + * always available. + */ + @Override + public boolean isDone() { + return true; + } + } + + /** + * Tests whether the specified {@link Throwable} is a checked exception. If + * not, an exception is thrown. + * + * @param ex the {@link Throwable} to check + * @return a flag whether the passed in exception is a checked exception + * @throws IllegalArgumentException if the {@link Throwable} is not a + * checked exception + */ + static Throwable checkedException(final Throwable ex) { + Validate.isTrue(ExceptionUtils.isChecked(ex), "Not a checked exception: " + ex); + return ex; + } + + /** + * Gets an implementation of {@link Future} that is immediately done + * and returns the specified constant value. + * + *

+ * This can be useful to return a simple constant immediately from the + * concurrent processing, perhaps as part of avoiding nulls. + * A constant future can also be useful in testing. + *

+ * + * @param the type of the value used by this {@link Future} object + * @param value the constant value to return, may be null + * @return an instance of Future that will return the value, never null + */ + public static Future constantFuture(final T value) { + return new ConstantFuture<>(value); + } + + /** + * Checks if a concurrent map contains a key and creates a corresponding + * value if not. This method first checks the presence of the key in the + * given map. If it is already contained, its value is returned. Otherwise + * the {@code get()} method of the passed in {@link ConcurrentInitializer} + * is called. With the resulting object + * {@link #putIfAbsent(ConcurrentMap, Object, Object)} is called. This + * handles the case that in the meantime another thread has added the key to + * the map. Both the map and the initializer can be null; in this + * case this method simply returns null. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentException if the initializer throws an exception + */ + public static V createIfAbsent(final ConcurrentMap map, final K key, + final ConcurrentInitializer init) throws ConcurrentException { + if (map == null || init == null) { + return null; + } + + final V value = map.get(key); + if (value == null) { + return putIfAbsent(map, key, init.get()); + } + return value; + } + + /** + * Checks if a concurrent map contains a key and creates a corresponding + * value if not, suppressing checked exceptions. This method calls + * {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it + * is caught and re-thrown as a {@link ConcurrentRuntimeException}. + * + * @param the type of the keys of the map + * @param the type of the values of the map + * @param map the map to be modified + * @param key the key of the value to be added + * @param init the {@link ConcurrentInitializer} for creating the value + * @return the value stored in the map after this operation; this may or may + * not be the object created by the {@link ConcurrentInitializer} + * @throws ConcurrentRuntimeException if the initializer throws an exception + */ + public static V createIfAbsentUnchecked(final ConcurrentMap map, + final K key, final ConcurrentInitializer init) { + try { + return createIfAbsent(map, key, init); + } catch (final ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } } /** @@ -130,20 +276,6 @@ public class ConcurrentUtils { } } - /** - * Tests whether the specified {@link Throwable} is a checked exception. If - * not, an exception is thrown. - * - * @param ex the {@link Throwable} to check - * @return a flag whether the passed in exception is a checked exception - * @throws IllegalArgumentException if the {@link Throwable} is not a - * checked exception - */ - static Throwable checkedException(final Throwable ex) { - Validate.isTrue(ExceptionUtils.isChecked(ex), "Not a checked exception: " + ex); - return ex; - } - /** * Invokes the specified {@link ConcurrentInitializer} and returns the * object produced by the initializer. This method just invokes the {@code @@ -225,142 +357,10 @@ public class ConcurrentUtils { } /** - * Checks if a concurrent map contains a key and creates a corresponding - * value if not. This method first checks the presence of the key in the - * given map. If it is already contained, its value is returned. Otherwise - * the {@code get()} method of the passed in {@link ConcurrentInitializer} - * is called. With the resulting object - * {@link #putIfAbsent(ConcurrentMap, Object, Object)} is called. This - * handles the case that in the meantime another thread has added the key to - * the map. Both the map and the initializer can be null; in this - * case this method simply returns null. - * - * @param the type of the keys of the map - * @param the type of the values of the map - * @param map the map to be modified - * @param key the key of the value to be added - * @param init the {@link ConcurrentInitializer} for creating the value - * @return the value stored in the map after this operation; this may or may - * not be the object created by the {@link ConcurrentInitializer} - * @throws ConcurrentException if the initializer throws an exception + * Private constructor so that no instances can be created. This class + * contains only static utility methods. */ - public static V createIfAbsent(final ConcurrentMap map, final K key, - final ConcurrentInitializer init) throws ConcurrentException { - if (map == null || init == null) { - return null; - } - - final V value = map.get(key); - if (value == null) { - return putIfAbsent(map, key, init.get()); - } - return value; - } - - /** - * Checks if a concurrent map contains a key and creates a corresponding - * value if not, suppressing checked exceptions. This method calls - * {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it - * is caught and re-thrown as a {@link ConcurrentRuntimeException}. - * - * @param the type of the keys of the map - * @param the type of the values of the map - * @param map the map to be modified - * @param key the key of the value to be added - * @param init the {@link ConcurrentInitializer} for creating the value - * @return the value stored in the map after this operation; this may or may - * not be the object created by the {@link ConcurrentInitializer} - * @throws ConcurrentRuntimeException if the initializer throws an exception - */ - public static V createIfAbsentUnchecked(final ConcurrentMap map, - final K key, final ConcurrentInitializer init) { - try { - return createIfAbsent(map, key, init); - } catch (final ConcurrentException cex) { - throw new ConcurrentRuntimeException(cex.getCause()); - } - } - - /** - * Gets an implementation of {@link Future} that is immediately done - * and returns the specified constant value. - * - *

- * This can be useful to return a simple constant immediately from the - * concurrent processing, perhaps as part of avoiding nulls. - * A constant future can also be useful in testing. - *

- * - * @param the type of the value used by this {@link Future} object - * @param value the constant value to return, may be null - * @return an instance of Future that will return the value, never null - */ - public static Future constantFuture(final T value) { - return new ConstantFuture<>(value); - } - - /** - * A specialized {@link Future} implementation which wraps a constant value. - * @param the type of the value wrapped by this class - */ - static final class ConstantFuture implements Future { - /** The constant value. */ - private final T value; - - /** - * Creates a new instance of {@link ConstantFuture} and initializes it - * with the constant value. - * - * @param value the value (may be null) - */ - ConstantFuture(final T value) { - this.value = value; - } - - /** - * {@inheritDoc} This implementation always returns true because - * the constant object managed by this {@link Future} implementation is - * always available. - */ - @Override - public boolean isDone() { - return true; - } - - /** - * {@inheritDoc} This implementation just returns the constant value. - */ - @Override - public T get() { - return value; - } - - /** - * {@inheritDoc} This implementation just returns the constant value; it - * does not block, therefore the timeout has no meaning. - */ - @Override - public T get(final long timeout, final TimeUnit unit) { - return value; - } - - /** - * {@inheritDoc} This implementation always returns false; there - * is no background process which could be cancelled. - */ - @Override - public boolean isCancelled() { - return false; - } - - /** - * {@inheritDoc} The cancel operation is not supported. This - * implementation always returns false. - */ - @Override - public boolean cancel(final boolean mayInterruptIfRunning) { - return false; - } + private ConcurrentUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java index 9f52aa009..cfa407b77 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java @@ -57,51 +57,6 @@ public class ConstantInitializer implements ConcurrentInitializer { object = obj; } - /** - * Directly returns the object that was passed to the constructor. This is - * the same object as returned by {@code get()}. However, this method does - * not declare that it throws an exception. - * - * @return the object managed by this initializer - */ - public final T getObject() { - return object; - } - - /** - * Returns the object managed by this initializer. This implementation just - * returns the object passed to the constructor. - * - * @return the object managed by this initializer - * @throws ConcurrentException if an error occurs - */ - @Override - public T get() throws ConcurrentException { - return getObject(); - } - - /** - * As a {@link ConstantInitializer} is initialized on construction this will - * always return true. - * - * @return true. - * @since 3.14.0 - */ - public boolean isInitialized() { - return true; - } - - /** - * Returns a hash code for this object. This implementation returns the hash - * code of the managed object. - * - * @return a hash code for this object - */ - @Override - public int hashCode() { - return Objects.hashCode(object); - } - /** * Compares this object with another one. This implementation returns * true if and only if the passed in object is an instance of @@ -124,6 +79,51 @@ public class ConstantInitializer implements ConcurrentInitializer { return Objects.equals(getObject(), c.getObject()); } + /** + * Returns the object managed by this initializer. This implementation just + * returns the object passed to the constructor. + * + * @return the object managed by this initializer + * @throws ConcurrentException if an error occurs + */ + @Override + public T get() throws ConcurrentException { + return getObject(); + } + + /** + * Directly returns the object that was passed to the constructor. This is + * the same object as returned by {@code get()}. However, this method does + * not declare that it throws an exception. + * + * @return the object managed by this initializer + */ + public final T getObject() { + return object; + } + + /** + * Returns a hash code for this object. This implementation returns the hash + * code of the managed object. + * + * @return a hash code for this object + */ + @Override + public int hashCode() { + return Objects.hashCode(object); + } + + /** + * As a {@link ConstantInitializer} is initialized on construction this will + * always return true. + * + * @return true. + * @since 3.14.0 + */ + public boolean isInitialized() { + return true; + } + /** * Returns a string representation for this object. This string also * contains a string representation of the object managed by this diff --git a/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java index 749020ac8..99e041871 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java @@ -137,9 +137,176 @@ import java.util.concurrent.atomic.AtomicReference; */ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { + /** + * An internally used data class holding information about the checks performed by + * this class. Basically, the number of received events and the start time of the + * current check interval are stored. + */ + private static final class CheckIntervalData { + /** The counter for events. */ + private final int eventCount; + + /** The start time of the current check interval. */ + private final long checkIntervalStart; + + /** + * Creates a new instance of {@link CheckIntervalData}. + * + * @param count the current count value + * @param intervalStart the start time of the check interval + */ + CheckIntervalData(final int count, final long intervalStart) { + eventCount = count; + checkIntervalStart = intervalStart; + } + + /** + * Returns the start time of the current check interval. + * + * @return the check interval start time + */ + public long getCheckIntervalStart() { + return checkIntervalStart; + } + + /** + * Returns the event counter. + * + * @return the number of received events + */ + public int getEventCount() { + return eventCount; + } + + /** + * Returns a new instance of {@link CheckIntervalData} with the event counter + * incremented by the given delta. If the delta is 0, this object is returned. + * + * @param delta the delta + * @return the updated instance + */ + public CheckIntervalData increment(final int delta) { + return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta, + getCheckIntervalStart()); + } + } + + /** + * Internally used class for executing check logic based on the current state of the + * circuit breaker. Having this logic extracted into special classes avoids complex + * if-then-else cascades. + */ + private abstract static class StateStrategy { + /** + * Obtains the check interval to applied for the represented state from the given + * {@link CircuitBreaker}. + * + * @param breaker the {@link CircuitBreaker} + * @return the check interval to be applied + */ + protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker); + + /** + * Returns a flag whether the end of the current check interval is reached. + * + * @param breaker the {@link CircuitBreaker} + * @param currentData the current state object + * @param now the current time + * @return a flag whether the end of the current check interval is reached + */ + public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker, + final CheckIntervalData currentData, final long now) { + return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker); + } + + /** + * Checks whether the specified {@link CheckIntervalData} objects indicate that a + * state transition should occur. Here the logic which checks for thresholds + * depending on the current state is implemented. + * + * @param breaker the {@link CircuitBreaker} + * @param currentData the current {@link CheckIntervalData} object + * @param nextData the updated {@link CheckIntervalData} object + * @return a flag whether a state transition should be performed + */ + public abstract boolean isStateTransition(EventCountCircuitBreaker breaker, + CheckIntervalData currentData, CheckIntervalData nextData); + } + + /** + * A specialized {@link StateStrategy} implementation for the state closed. + */ + private static final class StateStrategyClosed extends StateStrategy { + + /** + * {@inheritDoc} + */ + @Override + protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) { + return breaker.getOpeningInterval(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isStateTransition(final EventCountCircuitBreaker breaker, + final CheckIntervalData currentData, final CheckIntervalData nextData) { + return nextData.getEventCount() > breaker.getOpeningThreshold(); + } + } + + /** + * A specialized {@link StateStrategy} implementation for the state open. + */ + private static final class StateStrategyOpen extends StateStrategy { + /** + * {@inheritDoc} + */ + @Override + protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) { + return breaker.getClosingInterval(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isStateTransition(final EventCountCircuitBreaker breaker, + final CheckIntervalData currentData, final CheckIntervalData nextData) { + return nextData.getCheckIntervalStart() != currentData + .getCheckIntervalStart() + && currentData.getEventCount() < breaker.getClosingThreshold(); + } + } + /** A map for accessing the strategy objects for the different states. */ private static final Map STRATEGY_MAP = createStrategyMap(); + /** + * Creates the map with strategy objects. It allows access for a strategy for a given + * state. + * + * @return the strategy map + */ + private static Map createStrategyMap() { + final Map map = new EnumMap<>(State.class); + map.put(State.CLOSED, new StateStrategyClosed()); + map.put(State.OPEN, new StateStrategyOpen()); + return map; + } + + /** + * Returns the {@link StateStrategy} object responsible for the given state. + * + * @param state the state + * @return the corresponding {@link StateStrategy} + * @throws CircuitBreakingException if the strategy cannot be resolved + */ + private static StateStrategy stateStrategy(final State state) { + return STRATEGY_MAP.get(state); + } + /** Stores information about the current check interval. */ private final AtomicReference checkIntervalData; @@ -155,6 +322,39 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { /** The time interval for closing the circuit breaker. */ private final long closingInterval; + /** + * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for + * opening and closing checks. + * + * @param threshold the threshold for changing the status of the circuit breaker; if + * the number of events received in a check interval is greater than this value, the + * circuit breaker is opened; if it is lower than this value, it is closed again + * @param checkInterval the check interval for opening or closing the circuit breaker + * @param checkUnit the {@link TimeUnit} defining the check interval + */ + public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) { + this(threshold, checkInterval, checkUnit, threshold); + } + + /** + * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening + * and closing checks. + * + * @param openingThreshold the threshold for opening the circuit breaker; if this + * number of events is received in the time span determined by the check interval, the + * circuit breaker is opened + * @param checkInterval the check interval for opening or closing the circuit breaker + * @param checkUnit the {@link TimeUnit} defining the check interval + * @param closingThreshold the threshold for closing the circuit breaker; if the + * number of events received in the time span determined by the check interval goes + * below this threshold, the circuit breaker is closed again + */ + public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit, + final int closingThreshold) { + this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval, + checkUnit); + } + /** * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for * opening and closing it based on threshold values for events occurring in specific @@ -182,76 +382,14 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { } /** - * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening - * and closing checks. + * Changes the state of this circuit breaker and also initializes a new + * {@link CheckIntervalData} object. * - * @param openingThreshold the threshold for opening the circuit breaker; if this - * number of events is received in the time span determined by the check interval, the - * circuit breaker is opened - * @param checkInterval the check interval for opening or closing the circuit breaker - * @param checkUnit the {@link TimeUnit} defining the check interval - * @param closingThreshold the threshold for closing the circuit breaker; if the - * number of events received in the time span determined by the check interval goes - * below this threshold, the circuit breaker is closed again + * @param newState the new state to be set */ - public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit, - final int closingThreshold) { - this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval, - checkUnit); - } - - /** - * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for - * opening and closing checks. - * - * @param threshold the threshold for changing the status of the circuit breaker; if - * the number of events received in a check interval is greater than this value, the - * circuit breaker is opened; if it is lower than this value, it is closed again - * @param checkInterval the check interval for opening or closing the circuit breaker - * @param checkUnit the {@link TimeUnit} defining the check interval - */ - public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) { - this(threshold, checkInterval, checkUnit, threshold); - } - - /** - * Returns the threshold value for opening the circuit breaker. If this number of - * events is received in the time span determined by the opening interval, the circuit - * breaker is opened. - * - * @return the opening threshold - */ - public int getOpeningThreshold() { - return openingThreshold; - } - - /** - * Returns the interval (in nanoseconds) for checking for the opening threshold. - * - * @return the opening check interval - */ - public long getOpeningInterval() { - return openingInterval; - } - - /** - * Returns the threshold value for closing the circuit breaker. If the number of - * events received in the time span determined by the closing interval goes below this - * threshold, the circuit breaker is closed again. - * - * @return the closing threshold - */ - public int getClosingThreshold() { - return closingThreshold; - } - - /** - * Returns the interval (in nanoseconds) for checking for the closing threshold. - * - * @return the opening check interval - */ - public long getClosingInterval() { - return closingInterval; + private void changeStateAndStartNewCheckInterval(final State newState) { + changeState(newState); + checkIntervalData.set(new CheckIntervalData(0, nanoTime())); } /** @@ -269,10 +407,57 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { /** * {@inheritDoc} + *

+ * A new check interval is started. If too many events are received in + * this interval, the circuit breaker changes again to state open. If this circuit + * breaker is already closed, this method has no effect, except that a new check + * interval is started. + *

*/ @Override - public boolean incrementAndCheckState(final Integer increment) { - return performStateCheck(increment); + public void close() { + super.close(); + checkIntervalData.set(new CheckIntervalData(0, nanoTime())); + } + + /** + * Returns the interval (in nanoseconds) for checking for the closing threshold. + * + * @return the opening check interval + */ + public long getClosingInterval() { + return closingInterval; + } + + /** + * Returns the threshold value for closing the circuit breaker. If the number of + * events received in the time span determined by the closing interval goes below this + * threshold, the circuit breaker is closed again. + * + * @return the closing threshold + */ + public int getClosingThreshold() { + return closingThreshold; + } + + /** + * Returns the interval (in nanoseconds) for checking for the opening threshold. + * + * @return the opening check interval + */ + public long getOpeningInterval() { + return openingInterval; + } + + /** + * Returns the threshold value for opening the circuit breaker. If this number of + * events is received in the time span determined by the opening interval, the circuit + * breaker is opened. + * + * @return the opening threshold + */ + public int getOpeningThreshold() { + return openingThreshold; } /** @@ -287,6 +472,46 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { return incrementAndCheckState(1); } + /** + * {@inheritDoc} + */ + @Override + public boolean incrementAndCheckState(final Integer increment) { + return performStateCheck(increment); + } + + /** + * Returns the current time in nanoseconds. This method is used to obtain the current + * time. This is needed to calculate the check intervals correctly. + * + * @return the current time in nanoseconds + */ + long nanoTime() { + return System.nanoTime(); + } + + /** + * Calculates the next {@link CheckIntervalData} object based on the current data and + * the current state. The next data object takes the counter increment and the current + * time into account. + * + * @param increment the increment for the internal counter + * @param currentData the current check data object + * @param currentState the current state of the circuit breaker + * @param time the current time + * @return the updated {@link CheckIntervalData} object + */ + private CheckIntervalData nextCheckIntervalData(final int increment, + final CheckIntervalData currentData, final State currentState, final long time) { + final CheckIntervalData nextData; + if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) { + nextData = new CheckIntervalData(increment, time); + } else { + nextData = currentData.increment(increment); + } + return nextData; + } + /** * {@inheritDoc} *

@@ -302,21 +527,6 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { checkIntervalData.set(new CheckIntervalData(0, nanoTime())); } - /** - * {@inheritDoc} - *

- * A new check interval is started. If too many events are received in - * this interval, the circuit breaker changes again to state open. If this circuit - * breaker is already closed, this method has no effect, except that a new check - * interval is started. - *

- */ - @Override - public void close() { - super.close(); - checkIntervalData.set(new CheckIntervalData(0, nanoTime())); - } - /** * Actually checks the state of this circuit breaker and executes a state transition * if necessary. @@ -361,214 +571,4 @@ public class EventCountCircuitBreaker extends AbstractCircuitBreaker { || checkIntervalData.compareAndSet(currentData, nextData); } - /** - * Changes the state of this circuit breaker and also initializes a new - * {@link CheckIntervalData} object. - * - * @param newState the new state to be set - */ - private void changeStateAndStartNewCheckInterval(final State newState) { - changeState(newState); - checkIntervalData.set(new CheckIntervalData(0, nanoTime())); - } - - /** - * Calculates the next {@link CheckIntervalData} object based on the current data and - * the current state. The next data object takes the counter increment and the current - * time into account. - * - * @param increment the increment for the internal counter - * @param currentData the current check data object - * @param currentState the current state of the circuit breaker - * @param time the current time - * @return the updated {@link CheckIntervalData} object - */ - private CheckIntervalData nextCheckIntervalData(final int increment, - final CheckIntervalData currentData, final State currentState, final long time) { - final CheckIntervalData nextData; - if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) { - nextData = new CheckIntervalData(increment, time); - } else { - nextData = currentData.increment(increment); - } - return nextData; - } - - /** - * Returns the current time in nanoseconds. This method is used to obtain the current - * time. This is needed to calculate the check intervals correctly. - * - * @return the current time in nanoseconds - */ - long nanoTime() { - return System.nanoTime(); - } - - /** - * Returns the {@link StateStrategy} object responsible for the given state. - * - * @param state the state - * @return the corresponding {@link StateStrategy} - * @throws CircuitBreakingException if the strategy cannot be resolved - */ - private static StateStrategy stateStrategy(final State state) { - return STRATEGY_MAP.get(state); - } - - /** - * Creates the map with strategy objects. It allows access for a strategy for a given - * state. - * - * @return the strategy map - */ - private static Map createStrategyMap() { - final Map map = new EnumMap<>(State.class); - map.put(State.CLOSED, new StateStrategyClosed()); - map.put(State.OPEN, new StateStrategyOpen()); - return map; - } - - /** - * An internally used data class holding information about the checks performed by - * this class. Basically, the number of received events and the start time of the - * current check interval are stored. - */ - private static final class CheckIntervalData { - /** The counter for events. */ - private final int eventCount; - - /** The start time of the current check interval. */ - private final long checkIntervalStart; - - /** - * Creates a new instance of {@link CheckIntervalData}. - * - * @param count the current count value - * @param intervalStart the start time of the check interval - */ - CheckIntervalData(final int count, final long intervalStart) { - eventCount = count; - checkIntervalStart = intervalStart; - } - - /** - * Returns the event counter. - * - * @return the number of received events - */ - public int getEventCount() { - return eventCount; - } - - /** - * Returns the start time of the current check interval. - * - * @return the check interval start time - */ - public long getCheckIntervalStart() { - return checkIntervalStart; - } - - /** - * Returns a new instance of {@link CheckIntervalData} with the event counter - * incremented by the given delta. If the delta is 0, this object is returned. - * - * @param delta the delta - * @return the updated instance - */ - public CheckIntervalData increment(final int delta) { - return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta, - getCheckIntervalStart()); - } - } - - /** - * Internally used class for executing check logic based on the current state of the - * circuit breaker. Having this logic extracted into special classes avoids complex - * if-then-else cascades. - */ - private abstract static class StateStrategy { - /** - * Returns a flag whether the end of the current check interval is reached. - * - * @param breaker the {@link CircuitBreaker} - * @param currentData the current state object - * @param now the current time - * @return a flag whether the end of the current check interval is reached - */ - public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker, - final CheckIntervalData currentData, final long now) { - return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker); - } - - /** - * Checks whether the specified {@link CheckIntervalData} objects indicate that a - * state transition should occur. Here the logic which checks for thresholds - * depending on the current state is implemented. - * - * @param breaker the {@link CircuitBreaker} - * @param currentData the current {@link CheckIntervalData} object - * @param nextData the updated {@link CheckIntervalData} object - * @return a flag whether a state transition should be performed - */ - public abstract boolean isStateTransition(EventCountCircuitBreaker breaker, - CheckIntervalData currentData, CheckIntervalData nextData); - - /** - * Obtains the check interval to applied for the represented state from the given - * {@link CircuitBreaker}. - * - * @param breaker the {@link CircuitBreaker} - * @return the check interval to be applied - */ - protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker); - } - - /** - * A specialized {@link StateStrategy} implementation for the state closed. - */ - private static final class StateStrategyClosed extends StateStrategy { - - /** - * {@inheritDoc} - */ - @Override - public boolean isStateTransition(final EventCountCircuitBreaker breaker, - final CheckIntervalData currentData, final CheckIntervalData nextData) { - return nextData.getEventCount() > breaker.getOpeningThreshold(); - } - - /** - * {@inheritDoc} - */ - @Override - protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) { - return breaker.getOpeningInterval(); - } - } - - /** - * A specialized {@link StateStrategy} implementation for the state open. - */ - private static final class StateStrategyOpen extends StateStrategy { - /** - * {@inheritDoc} - */ - @Override - public boolean isStateTransition(final EventCountCircuitBreaker breaker, - final CheckIntervalData currentData, final CheckIntervalData nextData) { - return nextData.getCheckIntervalStart() != currentData - .getCheckIntervalStart() - && currentData.getEventCount() < breaker.getClosingThreshold(); - } - - /** - * {@inheritDoc} - */ - @Override - protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) { - return breaker.getClosingInterval(); - } - } - } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java b/src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java index 5adaf753d..439989d0a 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java @@ -26,10 +26,6 @@ import java.util.concurrent.FutureTask; */ public class FutureTasks { - private FutureTasks() { - // No instances needed. - } - /** * Creates a {@link FutureTask} and runs the given {@link Callable}. * @@ -42,4 +38,8 @@ public class FutureTasks { futureTask.run(); return futureTask; } + + private FutureTasks() { + // No instances needed. + } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java index acdfead21..5aee1f3b8 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java @@ -148,6 +148,14 @@ public class LazyInitializer extends AbstractConcurrentInitializer extends AbstractConcurrentInitializer { + /** + * A data class for storing the results of the background initialization + * performed by {@link MultiBackgroundInitializer}. Objects of this inner + * class are returned by {@link MultiBackgroundInitializer#initialize()}. + * They allow access to all result objects produced by the + * {@link BackgroundInitializer} objects managed by the owning instance. It + * is also possible to retrieve status information about single + * {@link BackgroundInitializer}s, i.e. whether they completed normally or + * caused an exception. + */ + public static class MultiBackgroundInitializerResults { + /** A map with the child initializers. */ + private final Map> initializers; + + /** A map with the result objects. */ + private final Map resultObjects; + + /** A map with the exceptions. */ + private final Map exceptions; + + /** + * Creates a new instance of {@link MultiBackgroundInitializerResults} + * and initializes it with maps for the {@link BackgroundInitializer} + * objects, their result objects and the exceptions thrown by them. + * + * @param inits the {@link BackgroundInitializer} objects + * @param results the result objects + * @param excepts the exceptions + */ + private MultiBackgroundInitializerResults( + final Map> inits, + final Map results, + final Map excepts) { + initializers = inits; + resultObjects = results; + exceptions = excepts; + } + + /** + * Checks whether an initializer with the given name exists. If not, + * throws an exception. If it exists, the associated child initializer + * is returned. + * + * @param name the name to check + * @return the initializer with this name + * @throws NoSuchElementException if the name is unknown + */ + private BackgroundInitializer checkName(final String name) { + final BackgroundInitializer init = initializers.get(name); + if (init == null) { + throw new NoSuchElementException( + "No child initializer with name " + name); + } + + return init; + } + + /** + * Returns the {@link ConcurrentException} object that was thrown by the + * {@link BackgroundInitializer} with the given name. If this + * initializer did not throw an exception, the return value is + * null. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@link BackgroundInitializer} + * @return the exception thrown by this initializer + * @throws NoSuchElementException if the name cannot be resolved + */ + public ConcurrentException getException(final String name) { + checkName(name); + return exceptions.get(name); + } + + /** + * Returns the {@link BackgroundInitializer} with the given name. If the + * name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@link BackgroundInitializer} + * @return the {@link BackgroundInitializer} with this name + * @throws NoSuchElementException if the name cannot be resolved + */ + public BackgroundInitializer getInitializer(final String name) { + return checkName(name); + } + + /** + * Returns the result object produced by the {@code + * BackgroundInitializer} with the given name. This is the object + * returned by the initializer's {@code initialize()} method. If this + * {@link BackgroundInitializer} caused an exception, null is + * returned. If the name cannot be resolved, an exception is thrown. + * + * @param name the name of the {@link BackgroundInitializer} + * @return the result object produced by this {@code + * BackgroundInitializer} + * @throws NoSuchElementException if the name cannot be resolved + */ + public Object getResultObject(final String name) { + checkName(name); + return resultObjects.get(name); + } + + /** + * Returns a set with the names of all {@link BackgroundInitializer} + * objects managed by the {@link MultiBackgroundInitializer}. + * + * @return an (unmodifiable) set with the names of the managed {@code + * BackgroundInitializer} objects + */ + public Set initializerNames() { + return Collections.unmodifiableSet(initializers.keySet()); + } + + /** + * Returns a flag whether the {@link BackgroundInitializer} with the + * given name caused an exception. + * + * @param name the name of the {@link BackgroundInitializer} + * @return a flag whether this initializer caused an exception + * @throws NoSuchElementException if the name cannot be resolved + */ + public boolean isException(final String name) { + checkName(name); + return exceptions.containsKey(name); + } + + /** + * Returns a flag whether the whole initialization was successful. This + * is the case if no child initializer has thrown an exception. + * + * @return a flag whether the initialization was successful + */ + public boolean isSuccessful() { + return exceptions.isEmpty(); + } + } + /** A map with the child initializers. */ private final Map> childInitializers = new HashMap<>(); @@ -142,6 +278,39 @@ public class MultiBackgroundInitializer } } + /** + * Calls the closer of all child {@code BackgroundInitializer} objects + * + * @throws ConcurrentException throws an ConcurrentException that will have all other exceptions as suppressed exceptions. ConcurrentException thrown by children will be unwrapped. + * @since 3.14.0 + */ + @Override + public void close() throws ConcurrentException { + ConcurrentException exception = null; + + for (BackgroundInitializer child : childInitializers.values()) { + try { + child.close(); + } catch (Exception e) { + if (exception == null) { + exception = new ConcurrentException(); + } + + if (e instanceof ConcurrentException) { + // Because ConcurrentException is only created by classes in this package + // we can safely unwrap it. + exception.addSuppressed(e.getCause()); + } else { + exception.addSuppressed(e); + } + } + } + + if (exception != null) { + throw exception; + } + } + /** * Returns the number of tasks needed for executing all child {@code * BackgroundInitializer} objects in parallel. This implementation sums up @@ -214,173 +383,4 @@ public class MultiBackgroundInitializer return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized); } - - /** - * Calls the closer of all child {@code BackgroundInitializer} objects - * - * @throws ConcurrentException throws an ConcurrentException that will have all other exceptions as suppressed exceptions. ConcurrentException thrown by children will be unwrapped. - * @since 3.14.0 - */ - @Override - public void close() throws ConcurrentException { - ConcurrentException exception = null; - - for (BackgroundInitializer child : childInitializers.values()) { - try { - child.close(); - } catch (Exception e) { - if (exception == null) { - exception = new ConcurrentException(); - } - - if (e instanceof ConcurrentException) { - // Because ConcurrentException is only created by classes in this package - // we can safely unwrap it. - exception.addSuppressed(e.getCause()); - } else { - exception.addSuppressed(e); - } - } - } - - if (exception != null) { - throw exception; - } - } - - /** - * A data class for storing the results of the background initialization - * performed by {@link MultiBackgroundInitializer}. Objects of this inner - * class are returned by {@link MultiBackgroundInitializer#initialize()}. - * They allow access to all result objects produced by the - * {@link BackgroundInitializer} objects managed by the owning instance. It - * is also possible to retrieve status information about single - * {@link BackgroundInitializer}s, i.e. whether they completed normally or - * caused an exception. - */ - public static class MultiBackgroundInitializerResults { - /** A map with the child initializers. */ - private final Map> initializers; - - /** A map with the result objects. */ - private final Map resultObjects; - - /** A map with the exceptions. */ - private final Map exceptions; - - /** - * Creates a new instance of {@link MultiBackgroundInitializerResults} - * and initializes it with maps for the {@link BackgroundInitializer} - * objects, their result objects and the exceptions thrown by them. - * - * @param inits the {@link BackgroundInitializer} objects - * @param results the result objects - * @param excepts the exceptions - */ - private MultiBackgroundInitializerResults( - final Map> inits, - final Map results, - final Map excepts) { - initializers = inits; - resultObjects = results; - exceptions = excepts; - } - - /** - * Returns the {@link BackgroundInitializer} with the given name. If the - * name cannot be resolved, an exception is thrown. - * - * @param name the name of the {@link BackgroundInitializer} - * @return the {@link BackgroundInitializer} with this name - * @throws NoSuchElementException if the name cannot be resolved - */ - public BackgroundInitializer getInitializer(final String name) { - return checkName(name); - } - - /** - * Returns the result object produced by the {@code - * BackgroundInitializer} with the given name. This is the object - * returned by the initializer's {@code initialize()} method. If this - * {@link BackgroundInitializer} caused an exception, null is - * returned. If the name cannot be resolved, an exception is thrown. - * - * @param name the name of the {@link BackgroundInitializer} - * @return the result object produced by this {@code - * BackgroundInitializer} - * @throws NoSuchElementException if the name cannot be resolved - */ - public Object getResultObject(final String name) { - checkName(name); - return resultObjects.get(name); - } - - /** - * Returns a flag whether the {@link BackgroundInitializer} with the - * given name caused an exception. - * - * @param name the name of the {@link BackgroundInitializer} - * @return a flag whether this initializer caused an exception - * @throws NoSuchElementException if the name cannot be resolved - */ - public boolean isException(final String name) { - checkName(name); - return exceptions.containsKey(name); - } - - /** - * Returns the {@link ConcurrentException} object that was thrown by the - * {@link BackgroundInitializer} with the given name. If this - * initializer did not throw an exception, the return value is - * null. If the name cannot be resolved, an exception is thrown. - * - * @param name the name of the {@link BackgroundInitializer} - * @return the exception thrown by this initializer - * @throws NoSuchElementException if the name cannot be resolved - */ - public ConcurrentException getException(final String name) { - checkName(name); - return exceptions.get(name); - } - - /** - * Returns a set with the names of all {@link BackgroundInitializer} - * objects managed by the {@link MultiBackgroundInitializer}. - * - * @return an (unmodifiable) set with the names of the managed {@code - * BackgroundInitializer} objects - */ - public Set initializerNames() { - return Collections.unmodifiableSet(initializers.keySet()); - } - - /** - * Returns a flag whether the whole initialization was successful. This - * is the case if no child initializer has thrown an exception. - * - * @return a flag whether the initialization was successful - */ - public boolean isSuccessful() { - return exceptions.isEmpty(); - } - - /** - * Checks whether an initializer with the given name exists. If not, - * throws an exception. If it exists, the associated child initializer - * is returned. - * - * @param name the name to check - * @return the initializer with this name - * @throws NoSuchElementException if the name is unknown - */ - private BackgroundInitializer checkName(final String name) { - final BackgroundInitializer init = initializers.get(name); - if (init == null) { - throw new NoSuchElementException( - "No child initializer with name " + name); - } - - return init; - } - } } diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java index 7c6148c05..ac12c6a3f 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java @@ -75,15 +75,6 @@ public class ThresholdCircuitBreaker extends AbstractCircuitBreaker { this.threshold = threshold; } - /** - * Gets the threshold. - * - * @return the threshold - */ - public long getThreshold() { - return threshold; - } - /** * {@inheritDoc} */ @@ -103,6 +94,15 @@ public class ThresholdCircuitBreaker extends AbstractCircuitBreaker { this.used.set(INITIAL_COUNT); } + /** + * Gets the threshold. + * + * @return the threshold + */ + public long getThreshold() { + return threshold; + } + /** * {@inheritDoc} * diff --git a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java index d768bfdc9..21e467486 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java @@ -230,63 +230,6 @@ public class TimedSemaphore { setLimit(limit); } - /** - * Returns the limit enforced by this semaphore. The limit determines how - * many invocations of {@link #acquire()} are allowed within the monitored - * period. - * - * @return the limit - */ - public final synchronized int getLimit() { - return limit; - } - - /** - * Sets the limit. This is the number of times the {@link #acquire()} method - * can be called within the time period specified. If this limit is reached, - * further invocations of {@link #acquire()} will block. Setting the limit - * to a value <= {@link #NO_LIMIT} will cause the limit to be disabled, - * i.e. an arbitrary number of{@link #acquire()} invocations is allowed in - * the time period. - * - * @param limit the limit - */ - public final synchronized void setLimit(final int limit) { - this.limit = limit; - } - - /** - * Initializes a shutdown. After that the object cannot be used anymore. - * This method can be invoked an arbitrary number of times. All invocations - * after the first one do not have any effect. - */ - public synchronized void shutdown() { - if (!shutdown) { - - if (ownExecutor) { - // if the executor was created by this instance, it has - // to be shutdown - getExecutorService().shutdownNow(); - } - if (task != null) { - task.cancel(false); - } - - shutdown = true; - } - } - - /** - * Tests whether the {@link #shutdown()} method has been called on this - * object. If this method returns true, this instance cannot be used - * any longer. - * - * @return a flag whether a shutdown has been performed - */ - public synchronized boolean isShutdown() { - return shutdown; - } - /** * Acquires a permit from this semaphore. This method will block if * the limit for the current period has already been reached. If @@ -311,33 +254,32 @@ public class TimedSemaphore { } /** - * Tries to acquire a permit from this semaphore. If the limit of this semaphore has - * not yet been reached, a permit is acquired, and this method returns - * true. Otherwise, this method returns immediately with the result - * false. + * Internal helper method for acquiring a permit. This method checks whether currently + * a permit can be acquired and - if so - increases the internal counter. The return + * value indicates whether a permit could be acquired. This method must be called with + * the lock of this object held. * - * @return true if a permit could be acquired; false - * otherwise - * @throws IllegalStateException if this semaphore is already shut down - * @since 3.5 + * @return a flag whether a permit could be acquired */ - public synchronized boolean tryAcquire() { - prepareAcquire(); - return acquirePermit(); + private boolean acquirePermit() { + if (getLimit() <= NO_LIMIT || acquireCount < getLimit()) { + acquireCount++; + return true; + } + return false; } /** - * Returns the number of (successful) acquire invocations during the last - * period. This is the number of times the {@link #acquire()} method was - * called without blocking. This can be useful for testing or debugging - * purposes or to determine a meaningful threshold value. If a limit is set, - * the value returned by this method won't be greater than this limit. - * - * @return the number of non-blocking invocations of the {@link #acquire()} - * method + * The current time period is finished. This method is called by the timer + * used internally to monitor the time period. It resets the counter and + * releases the threads waiting for this barrier. */ - public synchronized int getLastAcquiresPerPeriod() { - return lastCallsPerPeriod; + synchronized void endOfPeriod() { + lastCallsPerPeriod = acquireCount; + totalAcquireCount += acquireCount; + periodCount++; + acquireCount = 0; + notifyAll(); } /** @@ -379,6 +321,40 @@ public class TimedSemaphore { / (double) periodCount; } + /** + * Returns the executor service used by this instance. + * + * @return the executor service + */ + protected ScheduledExecutorService getExecutorService() { + return executorService; + } + + /** + * Returns the number of (successful) acquire invocations during the last + * period. This is the number of times the {@link #acquire()} method was + * called without blocking. This can be useful for testing or debugging + * purposes or to determine a meaningful threshold value. If a limit is set, + * the value returned by this method won't be greater than this limit. + * + * @return the number of non-blocking invocations of the {@link #acquire()} + * method + */ + public synchronized int getLastAcquiresPerPeriod() { + return lastCallsPerPeriod; + } + + /** + * Returns the limit enforced by this semaphore. The limit determines how + * many invocations of {@link #acquire()} are allowed within the monitored + * period. + * + * @return the limit + */ + public final synchronized int getLimit() { + return limit; + } + /** * Returns the time period. This is the time monitored by this semaphore. * Only a given number of invocations of the {@link #acquire()} method is @@ -400,36 +376,14 @@ public class TimedSemaphore { } /** - * Returns the executor service used by this instance. + * Tests whether the {@link #shutdown()} method has been called on this + * object. If this method returns true, this instance cannot be used + * any longer. * - * @return the executor service + * @return a flag whether a shutdown has been performed */ - protected ScheduledExecutorService getExecutorService() { - return executorService; - } - - /** - * Starts the timer. This method is called when {@link #acquire()} is called - * for the first time. It schedules a task to be executed at fixed rate to - * monitor the time period specified. - * - * @return a future object representing the task scheduled - */ - protected ScheduledFuture startTimer() { - return getExecutorService().scheduleAtFixedRate(this::endOfPeriod, getPeriod(), getPeriod(), getUnit()); - } - - /** - * The current time period is finished. This method is called by the timer - * used internally to monitor the time period. It resets the counter and - * releases the threads waiting for this barrier. - */ - synchronized void endOfPeriod() { - lastCallsPerPeriod = acquireCount; - totalAcquireCount += acquireCount; - periodCount++; - acquireCount = 0; - notifyAll(); + public synchronized boolean isShutdown() { + return shutdown; } /** @@ -447,18 +401,64 @@ public class TimedSemaphore { } /** - * Internal helper method for acquiring a permit. This method checks whether currently - * a permit can be acquired and - if so - increases the internal counter. The return - * value indicates whether a permit could be acquired. This method must be called with - * the lock of this object held. + * Sets the limit. This is the number of times the {@link #acquire()} method + * can be called within the time period specified. If this limit is reached, + * further invocations of {@link #acquire()} will block. Setting the limit + * to a value <= {@link #NO_LIMIT} will cause the limit to be disabled, + * i.e. an arbitrary number of{@link #acquire()} invocations is allowed in + * the time period. * - * @return a flag whether a permit could be acquired + * @param limit the limit */ - private boolean acquirePermit() { - if (getLimit() <= NO_LIMIT || acquireCount < getLimit()) { - acquireCount++; - return true; + public final synchronized void setLimit(final int limit) { + this.limit = limit; + } + + /** + * Initializes a shutdown. After that the object cannot be used anymore. + * This method can be invoked an arbitrary number of times. All invocations + * after the first one do not have any effect. + */ + public synchronized void shutdown() { + if (!shutdown) { + + if (ownExecutor) { + // if the executor was created by this instance, it has + // to be shutdown + getExecutorService().shutdownNow(); + } + if (task != null) { + task.cancel(false); + } + + shutdown = true; } - return false; + } + + /** + * Starts the timer. This method is called when {@link #acquire()} is called + * for the first time. It schedules a task to be executed at fixed rate to + * monitor the time period specified. + * + * @return a future object representing the task scheduled + */ + protected ScheduledFuture startTimer() { + return getExecutorService().scheduleAtFixedRate(this::endOfPeriod, getPeriod(), getPeriod(), getUnit()); + } + + /** + * Tries to acquire a permit from this semaphore. If the limit of this semaphore has + * not yet been reached, a permit is acquired, and this method returns + * true. Otherwise, this method returns immediately with the result + * false. + * + * @return true if a permit could be acquired; false + * otherwise + * @throws IllegalStateException if this semaphore is already shut down + * @since 3.5 + */ + public synchronized boolean tryAcquire() { + prepareAcquire(); + return acquirePermit(); } } diff --git a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java index 9a934ef91..029209eb0 100644 --- a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java +++ b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java @@ -69,9 +69,55 @@ import org.apache.commons.lang3.Validate; */ public class EventListenerSupport implements Serializable { + /** + * An invocation handler used to dispatch the event(s) to all the listeners. + */ + protected class ProxyInvocationHandler implements InvocationHandler { + + /** + * Propagates the method call to all registered listeners in place of the proxy listener object. + * + * @param unusedProxy the proxy object representing a listener on which the invocation was called; not used + * @param method the listener method that will be called on all of the listeners. + * @param args event arguments to propagate to the listeners. + * @return the result of the method call + * @throws InvocationTargetException if an error occurs + * @throws IllegalArgumentException if an error occurs + * @throws IllegalAccessException if an error occurs + */ + @Override + public Object invoke(final Object unusedProxy, final Method method, final Object[] args) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + for (final L listener : listeners) { + method.invoke(listener, args); + } + return null; + } + } + /** Serialization version */ private static final long serialVersionUID = 3593265990380473632L; + /** + * Creates an EventListenerSupport object which supports the specified + * listener type. + * + * @param the type of the listener interface + * @param listenerInterface the type of listener interface that will receive + * events posted using this class. + * + * @return an EventListenerSupport object which supports the specified + * listener type. + * + * @throws NullPointerException if {@code listenerInterface} is + * {@code null}. + * @throws IllegalArgumentException if {@code listenerInterface} is + * not an interface. + */ + public static EventListenerSupport create(final Class listenerInterface) { + return new EventListenerSupport<>(listenerInterface); + } + /** * The list used to hold the registered listeners. This list is * intentionally a thread-safe copy-on-write-array so that traversals over @@ -91,23 +137,10 @@ public class EventListenerSupport implements Serializable { private transient L[] prototypeArray; /** - * Creates an EventListenerSupport object which supports the specified - * listener type. - * - * @param the type of the listener interface - * @param listenerInterface the type of listener interface that will receive - * events posted using this class. - * - * @return an EventListenerSupport object which supports the specified - * listener type. - * - * @throws NullPointerException if {@code listenerInterface} is - * {@code null}. - * @throws IllegalArgumentException if {@code listenerInterface} is - * not an interface. + * Create a new EventListenerSupport instance. + * Serialization-friendly constructor. */ - public static EventListenerSupport create(final Class listenerInterface) { - return new EventListenerSupport<>(listenerInterface); + private EventListenerSupport() { } /** @@ -148,25 +181,6 @@ public class EventListenerSupport implements Serializable { initializeTransientFields(listenerInterface, classLoader); } - /** - * Create a new EventListenerSupport instance. - * Serialization-friendly constructor. - */ - private EventListenerSupport() { - } - - /** - * Returns a proxy object which can be used to call listener methods on all - * of the registered event listeners. All calls made to this proxy will be - * forwarded to all registered listeners. - * - * @return a proxy object which can be used to call listener methods on all - * of the registered event listeners - */ - public L fire() { - return proxy; - } - //********************************************************************************************************************** // Other Methods //********************************************************************************************************************** @@ -201,6 +215,37 @@ public class EventListenerSupport implements Serializable { } } + /** + * Create the {@link InvocationHandler} responsible for broadcasting calls + * to the managed listeners. Subclasses can override to provide custom behavior. + * @return ProxyInvocationHandler + */ + protected InvocationHandler createInvocationHandler() { + return new ProxyInvocationHandler(); + } + + /** + * Create the proxy object. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void createProxy(final Class listenerInterface, final ClassLoader classLoader) { + proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, + new Class[] { listenerInterface }, createInvocationHandler())); + } + + /** + * Returns a proxy object which can be used to call listener methods on all + * of the registered event listeners. All calls made to this proxy will be + * forwarded to all registered listeners. + * + * @return a proxy object which can be used to call listener methods on all + * of the registered event listeners + */ + public L fire() { + return proxy; + } + /** * Returns the number of registered listeners. * @@ -210,6 +255,44 @@ public class EventListenerSupport implements Serializable { return listeners.size(); } + /** + * Gets an array containing the currently registered listeners. + * Modification to this array's elements will have no effect on the + * {@link EventListenerSupport} instance. + * @return L[] + */ + public L[] getListeners() { + return listeners.toArray(prototypeArray); + } + + /** + * Initialize transient fields. + * @param listenerInterface the class of the listener interface + * @param classLoader the class loader to be used + */ + private void initializeTransientFields(final Class listenerInterface, final ClassLoader classLoader) { + // Will throw CCE here if not correct + this.prototypeArray = ArrayUtils.newInstance(listenerInterface, 0); + createProxy(listenerInterface, classLoader); + } + + /** + * Deserialize. + * @param objectInputStream the input stream + * @throws IOException if an IO error occurs + * @throws ClassNotFoundException if the class cannot be resolved + */ + private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { + @SuppressWarnings("unchecked") // Will throw CCE here if not correct + final L[] srcListeners = (L[]) objectInputStream.readObject(); + + this.listeners = new CopyOnWriteArrayList<>(srcListeners); + + final Class listenerInterface = ArrayUtils.getComponentType(srcListeners); + + initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); + } + /** * Unregisters an event listener. * @@ -223,16 +306,6 @@ public class EventListenerSupport implements Serializable { listeners.remove(listener); } - /** - * Gets an array containing the currently registered listeners. - * Modification to this array's elements will have no effect on the - * {@link EventListenerSupport} instance. - * @return L[] - */ - public L[] getListeners() { - return listeners.toArray(prototypeArray); - } - /** * Serialize. * @param objectOutputStream the output stream @@ -258,77 +331,4 @@ public class EventListenerSupport implements Serializable { */ objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray)); } - - /** - * Deserialize. - * @param objectInputStream the input stream - * @throws IOException if an IO error occurs - * @throws ClassNotFoundException if the class cannot be resolved - */ - private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { - @SuppressWarnings("unchecked") // Will throw CCE here if not correct - final L[] srcListeners = (L[]) objectInputStream.readObject(); - - this.listeners = new CopyOnWriteArrayList<>(srcListeners); - - final Class listenerInterface = ArrayUtils.getComponentType(srcListeners); - - initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); - } - - /** - * Initialize transient fields. - * @param listenerInterface the class of the listener interface - * @param classLoader the class loader to be used - */ - private void initializeTransientFields(final Class listenerInterface, final ClassLoader classLoader) { - // Will throw CCE here if not correct - this.prototypeArray = ArrayUtils.newInstance(listenerInterface, 0); - createProxy(listenerInterface, classLoader); - } - - /** - * Create the proxy object. - * @param listenerInterface the class of the listener interface - * @param classLoader the class loader to be used - */ - private void createProxy(final Class listenerInterface, final ClassLoader classLoader) { - proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, - new Class[] { listenerInterface }, createInvocationHandler())); - } - - /** - * Create the {@link InvocationHandler} responsible for broadcasting calls - * to the managed listeners. Subclasses can override to provide custom behavior. - * @return ProxyInvocationHandler - */ - protected InvocationHandler createInvocationHandler() { - return new ProxyInvocationHandler(); - } - - /** - * An invocation handler used to dispatch the event(s) to all the listeners. - */ - protected class ProxyInvocationHandler implements InvocationHandler { - - /** - * Propagates the method call to all registered listeners in place of the proxy listener object. - * - * @param unusedProxy the proxy object representing a listener on which the invocation was called; not used - * @param method the listener method that will be called on all of the listeners. - * @param args event arguments to propagate to the listeners. - * @return the result of the method call - * @throws InvocationTargetException if an error occurs - * @throws IllegalArgumentException if an error occurs - * @throws IllegalAccessException if an error occurs - */ - @Override - public Object invoke(final Object unusedProxy, final Method method, final Object[] args) - throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - for (final L listener : listeners) { - method.invoke(listener, args); - } - return null; - } - } } diff --git a/src/main/java/org/apache/commons/lang3/event/EventUtils.java b/src/main/java/org/apache/commons/lang3/event/EventUtils.java index 29d6795bb..c28c0b68a 100644 --- a/src/main/java/org/apache/commons/lang3/event/EventUtils.java +++ b/src/main/java/org/apache/commons/lang3/event/EventUtils.java @@ -34,6 +34,55 @@ import org.apache.commons.lang3.reflect.MethodUtils; */ public class EventUtils { + private static final class EventBindingInvocationHandler implements InvocationHandler { + private final Object target; + private final String methodName; + private final Set eventTypes; + + /** + * Creates a new instance of {@link EventBindingInvocationHandler}. + * + * @param target the target object for method invocations + * @param methodName the name of the method to be invoked + * @param eventTypes the names of the supported event types + */ + EventBindingInvocationHandler(final Object target, final String methodName, final String[] eventTypes) { + this.target = target; + this.methodName = methodName; + this.eventTypes = new HashSet<>(Arrays.asList(eventTypes)); + } + + /** + * Checks whether a method for the passed in parameters can be found. + * + * @param method the listener method invoked + * @return a flag whether the parameters could be matched + */ + private boolean hasMatchingParametersMethod(final Method method) { + return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null; + } + + /** + * Handles a method invocation on the proxy object. + * + * @param proxy the proxy instance + * @param method the method to be invoked + * @param parameters the parameters for the method invocation + * @return the result of the method call + * @throws Throwable if an error occurs + */ + @Override + public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable { + if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) { + if (hasMatchingParametersMethod(method)) { + return MethodUtils.invokeMethod(target, methodName, parameters); + } + return MethodUtils.invokeMethod(target, methodName); + } + return null; + } + } + /** * Adds an event listener to the specified source. This looks for an "add" method corresponding to the event * type (addActionListener, for example). @@ -77,53 +126,4 @@ public class EventUtils { new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes))); addEventListener(eventSource, listenerType, listener); } - - private static final class EventBindingInvocationHandler implements InvocationHandler { - private final Object target; - private final String methodName; - private final Set eventTypes; - - /** - * Creates a new instance of {@link EventBindingInvocationHandler}. - * - * @param target the target object for method invocations - * @param methodName the name of the method to be invoked - * @param eventTypes the names of the supported event types - */ - EventBindingInvocationHandler(final Object target, final String methodName, final String[] eventTypes) { - this.target = target; - this.methodName = methodName; - this.eventTypes = new HashSet<>(Arrays.asList(eventTypes)); - } - - /** - * Handles a method invocation on the proxy object. - * - * @param proxy the proxy instance - * @param method the method to be invoked - * @param parameters the parameters for the method invocation - * @return the result of the method call - * @throws Throwable if an error occurs - */ - @Override - public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable { - if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) { - if (hasMatchingParametersMethod(method)) { - return MethodUtils.invokeMethod(target, methodName, parameters); - } - return MethodUtils.invokeMethod(target, methodName); - } - return null; - } - - /** - * Checks whether a method for the passed in parameters can be found. - * - * @param method the listener method invoked - * @return a flag whether the parameters could be matched - */ - private boolean hasMatchingParametersMethod(final Method method) { - return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null; - } - } } diff --git a/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java b/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java index 7a4dcf26b..b158e99ce 100644 --- a/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java +++ b/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java @@ -35,15 +35,6 @@ public class CloneFailedException extends RuntimeException { super(message); } - /** - * Constructs a CloneFailedException. - * - * @param cause cause of the exception - */ - public CloneFailedException(final Throwable cause) { - super(cause); - } - /** * Constructs a CloneFailedException. * @@ -53,4 +44,13 @@ public class CloneFailedException extends RuntimeException { public CloneFailedException(final String message, final Throwable cause) { super(message, cause); } + + /** + * Constructs a CloneFailedException. + * + * @param cause cause of the exception + */ + public CloneFailedException(final Throwable cause) { + super(cause); + } } diff --git a/src/main/java/org/apache/commons/lang3/exception/ContextedException.java b/src/main/java/org/apache/commons/lang3/exception/ContextedException.java index b4c94537d..980192449 100644 --- a/src/main/java/org/apache/commons/lang3/exception/ContextedException.java +++ b/src/main/java/org/apache/commons/lang3/exception/ContextedException.java @@ -111,18 +111,6 @@ public class ContextedException extends Exception implements ExceptionContext { 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(final Throwable cause) { - super(cause); - exceptionContext = new DefaultExceptionContext(); - } - /** * Instantiates ContextedException with cause and message. *

@@ -151,6 +139,18 @@ public class ContextedException extends Exception implements ExceptionContext { exceptionContext = context; } + /** + * 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(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + /** * 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 @@ -171,22 +171,19 @@ public class ContextedException extends Exception implements ExceptionContext { } /** - * 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} + * {@inheritDoc} */ @Override - public ContextedException setContextValue(final String label, final Object value) { - exceptionContext.setContextValue(label, value); - return this; + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); } /** @@ -209,16 +206,8 @@ public class ContextedException extends Exception implements ExceptionContext { * {@inheritDoc} */ @Override - public List> getContextEntries() { - return this.exceptionContext.getContextEntries(); - } - - /** - * {@inheritDoc} - */ - @Override - public Set getContextLabels() { - return exceptionContext.getContextLabels(); + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); } /** @@ -244,10 +233,21 @@ public class ContextedException extends Exception implements ExceptionContext { } /** - * {@inheritDoc} + * 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} */ @Override - public String getFormattedExceptionMessage(final String baseMessage) { - return exceptionContext.getFormattedExceptionMessage(baseMessage); + public ContextedException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; } } diff --git a/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java b/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java index 59e4e5990..a29609d01 100644 --- a/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java +++ b/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java @@ -111,18 +111,6 @@ public class ContextedRuntimeException extends RuntimeException implements Excep 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(final Throwable cause) { - super(cause); - exceptionContext = new DefaultExceptionContext(); - } - /** * Instantiates ContextedRuntimeException with cause and message. *

@@ -151,6 +139,18 @@ public class ContextedRuntimeException extends RuntimeException implements Excep exceptionContext = context; } + /** + * 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(final Throwable cause) { + super(cause); + exceptionContext = new DefaultExceptionContext(); + } + /** * 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 @@ -171,22 +171,19 @@ public class ContextedRuntimeException extends RuntimeException implements Excep } /** - * 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} + * {@inheritDoc} */ @Override - public ContextedRuntimeException setContextValue(final String label, final Object value) { - exceptionContext.setContextValue(label, value); - return this; + public List> getContextEntries() { + return this.exceptionContext.getContextEntries(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContextLabels() { + return exceptionContext.getContextLabels(); } /** @@ -209,16 +206,8 @@ public class ContextedRuntimeException extends RuntimeException implements Excep * {@inheritDoc} */ @Override - public List> getContextEntries() { - return this.exceptionContext.getContextEntries(); - } - - /** - * {@inheritDoc} - */ - @Override - public Set getContextLabels() { - return exceptionContext.getContextLabels(); + public String getFormattedExceptionMessage(final String baseMessage) { + return exceptionContext.getFormattedExceptionMessage(baseMessage); } /** @@ -244,11 +233,22 @@ public class ContextedRuntimeException extends RuntimeException implements Excep } /** - * {@inheritDoc} + * 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} */ @Override - public String getFormattedExceptionMessage(final String baseMessage) { - return exceptionContext.getFormattedExceptionMessage(baseMessage); + public ContextedRuntimeException setContextValue(final String label, final Object value) { + exceptionContext.setContextValue(label, value); + return this; } } diff --git a/src/main/java/org/apache/commons/lang3/function/Consumers.java b/src/main/java/org/apache/commons/lang3/function/Consumers.java index 19b5efd9e..1d505d636 100644 --- a/src/main/java/org/apache/commons/lang3/function/Consumers.java +++ b/src/main/java/org/apache/commons/lang3/function/Consumers.java @@ -31,10 +31,6 @@ public class Consumers { @SuppressWarnings("rawtypes") private static final Consumer NOP = Function.identity()::apply; - private Consumers() { - // No instances. - } - /** * Gets the NOP Consumer singleton. * @@ -46,4 +42,8 @@ public class Consumers { return NOP; } + private Consumers() { + // No instances. + } + } diff --git a/src/main/java/org/apache/commons/lang3/function/Suppliers.java b/src/main/java/org/apache/commons/lang3/function/Suppliers.java index a19a1598d..7a5fecd4b 100644 --- a/src/main/java/org/apache/commons/lang3/function/Suppliers.java +++ b/src/main/java/org/apache/commons/lang3/function/Suppliers.java @@ -35,6 +35,17 @@ public class Suppliers { @SuppressWarnings("rawtypes") private static Supplier NUL = () -> null; + /** + * Null-safe call to {@link Supplier#get()}. + * + * @param the type of results supplied by this supplier. + * @param supplier the supplier or null. + * @return Result of {@link Supplier#get()} or null. + */ + public static T get(final Supplier supplier) { + return supplier == null ? null : supplier.get(); + } + /** * Returns the singleton supplier that always returns null. *

@@ -50,15 +61,4 @@ public class Suppliers { return NUL; } - /** - * Null-safe call to {@link Supplier#get()}. - * - * @param the type of results supplied by this supplier. - * @param supplier the supplier or null. - * @return Result of {@link Supplier#get()} or null. - */ - public static T get(final Supplier supplier) { - return supplier == null ? null : supplier.get(); - } - } diff --git a/src/main/java/org/apache/commons/lang3/function/TriFunction.java b/src/main/java/org/apache/commons/lang3/function/TriFunction.java index 7d8b81c50..9aa09e3ed 100644 --- a/src/main/java/org/apache/commons/lang3/function/TriFunction.java +++ b/src/main/java/org/apache/commons/lang3/function/TriFunction.java @@ -39,16 +39,6 @@ import java.util.function.Function; @FunctionalInterface public interface TriFunction { - /** - * Applies this function to the given arguments. - * - * @param t the first function argument - * @param u the second function argument - * @param v the third function argument - * @return the function result - */ - R apply(T t, U u, V v); - /** * Returns a composed function that first applies this function to its input, and then applies the {@code after} * function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the @@ -63,4 +53,14 @@ public interface TriFunction { Objects.requireNonNull(after); return (final T t, final U u, final V v) -> after.apply(apply(t, u, v)); } + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @param v the third function argument + * @return the function result + */ + R apply(T t, U u, V v); } diff --git a/src/main/java/org/apache/commons/lang3/math/Fraction.java b/src/main/java/org/apache/commons/lang3/math/Fraction.java index c362a162f..cf7a2d546 100644 --- a/src/main/java/org/apache/commons/lang3/math/Fraction.java +++ b/src/main/java/org/apache/commons/lang3/math/Fraction.java @@ -92,143 +92,21 @@ public final class Fraction extends Number implements Comparable { /** - * The numerator number part of the fraction (the three in three sevenths). - */ - private final int numerator; - /** - * The denominator number part of the fraction (the seven in three sevenths). - */ - private final int denominator; - - /** - * Cached output hashCode (class is immutable). - */ - private transient int hashCode; - /** - * Cached output toString (class is immutable). - */ - private transient String toString; - /** - * Cached output toProperString (class is immutable). - */ - private transient String toProperString; - - /** - * Constructs a {@link Fraction} instance with the 2 parts - * of a fraction Y/Z. + * Add two integers, checking for overflow. * - * @param numerator the numerator, for example the three in 'three sevenths' - * @param denominator the denominator, for example the seven in 'three sevenths' + * @param x an addend + * @param y an addend + * @return the sum {@code x+y} + * @throws ArithmeticException if the result can not be represented as + * an int */ - private Fraction(final int numerator, final int denominator) { - this.numerator = numerator; - this.denominator = denominator; + private static int addAndCheck(final int x, final int y) { + final long s = (long) x + (long) y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: add"); + } + return (int) s; } - - /** - * Creates a {@link Fraction} instance with the 2 parts - * of a fraction Y/Z. - * - *

Any negative signs are resolved to be on the numerator.

- * - * @param numerator the numerator, for example the three in 'three sevenths' - * @param denominator the denominator, for example the seven in 'three sevenths' - * @return a new fraction instance - * @throws ArithmeticException if the denominator is {@code zero} - * or the denominator is {@code negative} and the numerator is {@code Integer#MIN_VALUE} - */ - public static Fraction getFraction(int numerator, int denominator) { - if (denominator == 0) { - throw new ArithmeticException("The denominator must not be zero"); - } - if (denominator < 0) { - if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { - throw new ArithmeticException("overflow: can't negate"); - } - numerator = -numerator; - denominator = -denominator; - } - return new Fraction(numerator, denominator); - } - - /** - * Creates a {@link Fraction} instance with the 3 parts - * of a fraction X Y/Z. - * - *

The negative sign must be passed in on the whole number part.

- * - * @param whole the whole number, for example the one in 'one and three sevenths' - * @param numerator the numerator, for example the three in 'one and three sevenths' - * @param denominator the denominator, for example the seven in 'one and three sevenths' - * @return a new fraction instance - * @throws ArithmeticException if the denominator is {@code zero} - * @throws ArithmeticException if the denominator is negative - * @throws ArithmeticException if the numerator is negative - * @throws ArithmeticException if the resulting numerator exceeds - * {@code Integer.MAX_VALUE} - */ - public static Fraction getFraction(final int whole, final int numerator, final int denominator) { - if (denominator == 0) { - throw new ArithmeticException("The denominator must not be zero"); - } - if (denominator < 0) { - throw new ArithmeticException("The denominator must not be negative"); - } - if (numerator < 0) { - throw new ArithmeticException("The numerator must not be negative"); - } - final long numeratorValue; - if (whole < 0) { - numeratorValue = whole * (long) denominator - numerator; - } else { - numeratorValue = whole * (long) denominator + numerator; - } - if (numeratorValue < Integer.MIN_VALUE || numeratorValue > Integer.MAX_VALUE) { - throw new ArithmeticException("Numerator too large to represent as an Integer."); - } - return new Fraction((int) numeratorValue, denominator); - } - - /** - * Creates a reduced {@link Fraction} instance with the 2 parts - * of a fraction Y/Z. - * - *

For example, if the input parameters represent 2/4, then the created - * fraction will be 1/2.

- * - *

Any negative signs are resolved to be on the numerator.

- * - * @param numerator the numerator, for example the three in 'three sevenths' - * @param denominator the denominator, for example the seven in 'three sevenths' - * @return a new fraction instance, with the numerator and denominator reduced - * @throws ArithmeticException if the denominator is {@code zero} - */ - public static Fraction getReducedFraction(int numerator, int denominator) { - if (denominator == 0) { - throw new ArithmeticException("The denominator must not be zero"); - } - if (numerator == 0) { - return ZERO; // normalize zero. - } - // allow 2^k/-2^31 as a valid fraction (where k>0) - if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { - numerator /= 2; - denominator /= 2; - } - if (denominator < 0) { - if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { - throw new ArithmeticException("overflow: can't negate"); - } - numerator = -numerator; - denominator = -denominator; - } - // simplify fraction. - final int gcd = greatestCommonDivisor(numerator, denominator); - numerator /= gcd; - denominator /= gcd; - return new Fraction(numerator, denominator); - } - /** * Creates a {@link Fraction} instance from a {@code double} value. * @@ -291,6 +169,68 @@ public final class Fraction extends Number implements Comparable { return getReducedFraction((numer0 + wholeNumber * denom0) * sign, denom0); } + /** + * Creates a {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. + * + *

Any negative signs are resolved to be on the numerator.

+ * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is {@code zero} + * or the denominator is {@code negative} and the numerator is {@code Integer#MIN_VALUE} + */ + public static Fraction getFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + return new Fraction(numerator, denominator); + } + /** + * Creates a {@link Fraction} instance with the 3 parts + * of a fraction X Y/Z. + * + *

The negative sign must be passed in on the whole number part.

+ * + * @param whole the whole number, for example the one in 'one and three sevenths' + * @param numerator the numerator, for example the three in 'one and three sevenths' + * @param denominator the denominator, for example the seven in 'one and three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is {@code zero} + * @throws ArithmeticException if the denominator is negative + * @throws ArithmeticException if the numerator is negative + * @throws ArithmeticException if the resulting numerator exceeds + * {@code Integer.MAX_VALUE} + */ + public static Fraction getFraction(final int whole, final int numerator, final int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + throw new ArithmeticException("The denominator must not be negative"); + } + if (numerator < 0) { + throw new ArithmeticException("The numerator must not be negative"); + } + final long numeratorValue; + if (whole < 0) { + numeratorValue = whole * (long) denominator - numerator; + } else { + numeratorValue = whole * (long) denominator + numerator; + } + if (numeratorValue < Integer.MIN_VALUE || numeratorValue > Integer.MAX_VALUE) { + throw new ArithmeticException("Numerator too large to represent as an Integer."); + } + return new Fraction((int) numeratorValue, denominator); + } /** * Creates a Fraction from a {@link String}. * @@ -343,203 +283,43 @@ public final class Fraction extends Number implements Comparable { } /** - * Gets the numerator part of the fraction. + * Creates a reduced {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. * - *

This method may return a value greater than the denominator, an - * improper fraction, such as the seven in 7/4.

+ *

For example, if the input parameters represent 2/4, then the created + * fraction will be 1/2.

* - * @return the numerator fraction part + *

Any negative signs are resolved to be on the numerator.

+ * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws ArithmeticException if the denominator is {@code zero} */ - public int getNumerator() { - return numerator; - } - - /** - * Gets the denominator part of the fraction. - * - * @return the denominator fraction part - */ - public int getDenominator() { - return denominator; - } - - /** - * Gets the proper numerator, always positive. - * - *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. - * This method returns the 3 from the proper fraction.

- * - *

If the fraction is negative such as -7/4, it can be resolved into - * -1 3/4, so this method returns the positive proper numerator, 3.

- * - * @return the numerator fraction part of a proper fraction, always positive - */ - public int getProperNumerator() { - return Math.abs(numerator % denominator); - } - - /** - * Gets the proper whole part of the fraction. - * - *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. - * This method returns the 1 from the proper fraction.

- * - *

If the fraction is negative such as -7/4, it can be resolved into - * -1 3/4, so this method returns the positive whole part -1.

- * - * @return the whole fraction part of a proper fraction, that includes the sign - */ - public int getProperWhole() { - return numerator / denominator; - } - - /** - * Gets the fraction as an {@code int}. This returns the whole number - * part of the fraction. - * - * @return the whole number fraction part - */ - @Override - public int intValue() { - return numerator / denominator; - } - - /** - * Gets the fraction as a {@code long}. This returns the whole number - * part of the fraction. - * - * @return the whole number fraction part - */ - @Override - public long longValue() { - return (long) numerator / denominator; - } - - /** - * Gets the fraction as a {@code float}. This calculates the fraction - * as the numerator divided by denominator. - * - * @return the fraction as a {@code float} - */ - @Override - public float floatValue() { - return (float) numerator / (float) denominator; - } - - /** - * Gets the fraction as a {@code double}. This calculates the fraction - * as the numerator divided by denominator. - * - * @return the fraction as a {@code double} - */ - @Override - public double doubleValue() { - return (double) numerator / (double) denominator; - } - - /** - * Reduce the fraction to the smallest values for the numerator and - * denominator, returning the result. - * - *

For example, if this fraction represents 2/4, then the result - * will be 1/2.

- * - * @return a new reduced fraction instance, or this if no simplification possible - */ - public Fraction reduce() { + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } if (numerator == 0) { - return equals(ZERO) ? this : ZERO; + return ZERO; // normalize zero. } - final int gcd = greatestCommonDivisor(Math.abs(numerator), denominator); - if (gcd == 1) { - return this; + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { + numerator /= 2; + denominator /= 2; } - return getFraction(numerator / gcd, denominator / gcd); - } - - /** - * Gets a fraction that is the inverse (1/fraction) of this one. - * - *

The returned fraction is not reduced.

- * - * @return a new fraction instance with the numerator and denominator - * inverted. - * @throws ArithmeticException if the fraction represents zero. - */ - public Fraction invert() { - if (numerator == 0) { - throw new ArithmeticException("Unable to invert zero."); - } - if (numerator==Integer.MIN_VALUE) { - throw new ArithmeticException("overflow: can't negate numerator"); - } - if (numerator<0) { - return new Fraction(-denominator, -numerator); - } - return new Fraction(denominator, numerator); - } - - /** - * Gets a fraction that is the negative (-fraction) of this one. - * - *

The returned fraction is not reduced.

- * - * @return a new fraction instance with the opposite signed numerator - */ - public Fraction negate() { - // the positive range is one smaller than the negative range of an int. - if (numerator==Integer.MIN_VALUE) { - throw new ArithmeticException("overflow: too large to negate"); - } - return new Fraction(-numerator, denominator); - } - - /** - * Gets a fraction that is the positive equivalent of this one. - *

More precisely: {@code (fraction >= 0 ? this : -fraction)}

- * - *

The returned fraction is not reduced.

- * - * @return {@code this} if it is positive, or a new positive fraction - * instance with the opposite signed numerator - */ - public Fraction abs() { - if (numerator >= 0) { - return this; - } - return negate(); - } - - /** - * Gets a fraction that is raised to the passed in power. - * - *

The returned fraction is in reduced form.

- * - * @param power the power to raise the fraction to - * @return {@code this} if the power is one, {@link #ONE} if the power - * is zero (even if the fraction equals ZERO) or a new fraction instance - * raised to the appropriate power - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * {@code Integer.MAX_VALUE} - */ - public Fraction pow(final int power) { - if (power == 1) { - return this; - } - if (power == 0) { - return ONE; - } - if (power < 0) { - if (power == Integer.MIN_VALUE) { // MIN_VALUE can't be negated. - return this.invert().pow(2).pow(-(power / 2)); + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); } - return this.invert().pow(-power); + numerator = -numerator; + denominator = -denominator; } - final Fraction f = this.multiplyBy(this); - if (power % 2 == 0) { // if even... - return f.pow(power / 2); - } - return f.pow(power / 2).multiplyBy(this); + // simplify fraction. + final int gcd = greatestCommonDivisor(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); } /** @@ -644,23 +424,6 @@ public final class Fraction extends Number implements Comparable { return (int) m; } - /** - * Add two integers, checking for overflow. - * - * @param x an addend - * @param y an addend - * @return the sum {@code x+y} - * @throws ArithmeticException if the result can not be represented as - * an int - */ - private static int addAndCheck(final int x, final int y) { - final long s = (long) x + (long) y; - if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { - throw new ArithmeticException("overflow: add"); - } - return (int) s; - } - /** * Subtract two integers, checking for overflow. * @@ -678,6 +441,59 @@ public final class Fraction extends Number implements Comparable { return (int) s; } + /** + * The numerator number part of the fraction (the three in three sevenths). + */ + private final int numerator; + + /** + * The denominator number part of the fraction (the seven in three sevenths). + */ + private final int denominator; + + /** + * Cached output hashCode (class is immutable). + */ + private transient int hashCode; + + /** + * Cached output toString (class is immutable). + */ + private transient String toString; + + /** + * Cached output toProperString (class is immutable). + */ + private transient String toProperString; + + /** + * Constructs a {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + */ + private Fraction(final int numerator, final int denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + /** + * Gets a fraction that is the positive equivalent of this one. + *

More precisely: {@code (fraction >= 0 ? this : -fraction)}

+ * + *

The returned fraction is not reduced.

+ * + * @return {@code this} if it is positive, or a new positive fraction + * instance with the opposite signed numerator + */ + public Fraction abs() { + if (numerator >= 0) { + return this; + } + return negate(); + } + /** * Adds the value of this fraction to another, returning the result in reduced form. * The algorithm follows Knuth, 4.5.1. @@ -692,20 +508,6 @@ public final class Fraction extends Number implements Comparable { return addSub(fraction, true /* add */); } - /** - * Subtracts the value of another fraction from the value of this one, - * returning the result in reduced form. - * - * @param fraction the fraction to subtract, must not be {@code null} - * @return a {@link Fraction} instance with the resulting values - * @throws NullPointerException if the fraction is {@code null} - * @throws ArithmeticException if the resulting numerator or denominator - * cannot be represented in an {@code int}. - */ - public Fraction subtract(final Fraction fraction) { - return addSub(fraction, false /* subtract */); - } - /** * Implement add and subtract using algorithm described in Knuth 4.5.1. * @@ -754,81 +556,6 @@ public final class Fraction extends Number implements Comparable { return new Fraction(w.intValue(), mulPosAndCheck(denominator / d1, fraction.denominator / d2)); } - /** - * Multiplies the value of this fraction by another, returning the - * result in reduced form. - * - * @param fraction the fraction to multiply by, must not be {@code null} - * @return a {@link Fraction} instance with the resulting values - * @throws NullPointerException if the fraction is {@code null} - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * {@code Integer.MAX_VALUE} - */ - public Fraction multiplyBy(final Fraction fraction) { - Objects.requireNonNull(fraction, "fraction"); - if (numerator == 0 || fraction.numerator == 0) { - return ZERO; - } - // knuth 4.5.1 - // make sure we don't overflow unless the result *must* overflow. - final int d1 = greatestCommonDivisor(numerator, fraction.denominator); - final int d2 = greatestCommonDivisor(fraction.numerator, denominator); - return getReducedFraction(mulAndCheck(numerator / d1, fraction.numerator / d2), - mulPosAndCheck(denominator / d2, fraction.denominator / d1)); - } - - /** - * Divide the value of this fraction by another. - * - * @param fraction the fraction to divide by, must not be {@code null} - * @return a {@link Fraction} instance with the resulting values - * @throws NullPointerException if the fraction is {@code null} - * @throws ArithmeticException if the fraction to divide by is zero - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * {@code Integer.MAX_VALUE} - */ - public Fraction divideBy(final Fraction fraction) { - Objects.requireNonNull(fraction, "fraction"); - if (fraction.numerator == 0) { - throw new ArithmeticException("The fraction to divide by must not be zero"); - } - return multiplyBy(fraction.invert()); - } - - /** - * Compares this fraction to another object to test if they are equal.. - * - *

To be equal, both values must be equal. Thus 2/4 is not equal to 1/2.

- * - * @param obj the reference object with which to compare - * @return {@code true} if this object is equal - */ - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Fraction)) { - return false; - } - final Fraction other = (Fraction) obj; - return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator(); - } - - /** - * Gets a hashCode for the fraction. - * - * @return a hash code value for this object - */ - @Override - public int hashCode() { - if (hashCode == 0) { - // hash code update should be atomic. - hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator(); - } - return hashCode; - } - /** * Compares this object to another based on size. * @@ -857,18 +584,276 @@ public final class Fraction extends Number implements Comparable { } /** - * Gets the fraction as a {@link String}. + * Divide the value of this fraction by another. * - *

The format used is 'numerator/denominator' always. + * @param fraction the fraction to divide by, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws NullPointerException if the fraction is {@code null} + * @throws ArithmeticException if the fraction to divide by is zero + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction divideBy(final Fraction fraction) { + Objects.requireNonNull(fraction, "fraction"); + if (fraction.numerator == 0) { + throw new ArithmeticException("The fraction to divide by must not be zero"); + } + return multiplyBy(fraction.invert()); + } + + /** + * Gets the fraction as a {@code double}. This calculates the fraction + * as the numerator divided by denominator. * - * @return a {@link String} form of the fraction + * @return the fraction as a {@code double} */ @Override - public String toString() { - if (toString == null) { - toString = getNumerator() + "/" + getDenominator(); + public double doubleValue() { + return (double) numerator / (double) denominator; + } + + /** + * Compares this fraction to another object to test if they are equal.. + * + *

To be equal, both values must be equal. Thus 2/4 is not equal to 1/2.

+ * + * @param obj the reference object with which to compare + * @return {@code true} if this object is equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; } - return toString; + if (!(obj instanceof Fraction)) { + return false; + } + final Fraction other = (Fraction) obj; + return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator(); + } + + /** + * Gets the fraction as a {@code float}. This calculates the fraction + * as the numerator divided by denominator. + * + * @return the fraction as a {@code float} + */ + @Override + public float floatValue() { + return (float) numerator / (float) denominator; + } + + /** + * Gets the denominator part of the fraction. + * + * @return the denominator fraction part + */ + public int getDenominator() { + return denominator; + } + + /** + * Gets the numerator part of the fraction. + * + *

This method may return a value greater than the denominator, an + * improper fraction, such as the seven in 7/4.

+ * + * @return the numerator fraction part + */ + public int getNumerator() { + return numerator; + } + + /** + * Gets the proper numerator, always positive. + * + *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 3 from the proper fraction.

+ * + *

If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive proper numerator, 3.

+ * + * @return the numerator fraction part of a proper fraction, always positive + */ + public int getProperNumerator() { + return Math.abs(numerator % denominator); + } + + /** + * Gets the proper whole part of the fraction. + * + *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 1 from the proper fraction.

+ * + *

If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive whole part -1.

+ * + * @return the whole fraction part of a proper fraction, that includes the sign + */ + public int getProperWhole() { + return numerator / denominator; + } + + /** + * Gets a hashCode for the fraction. + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (hashCode == 0) { + // hash code update should be atomic. + hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator(); + } + return hashCode; + } + + /** + * Gets the fraction as an {@code int}. This returns the whole number + * part of the fraction. + * + * @return the whole number fraction part + */ + @Override + public int intValue() { + return numerator / denominator; + } + + /** + * Gets a fraction that is the inverse (1/fraction) of this one. + * + *

The returned fraction is not reduced.

+ * + * @return a new fraction instance with the numerator and denominator + * inverted. + * @throws ArithmeticException if the fraction represents zero. + */ + public Fraction invert() { + if (numerator == 0) { + throw new ArithmeticException("Unable to invert zero."); + } + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate numerator"); + } + if (numerator<0) { + return new Fraction(-denominator, -numerator); + } + return new Fraction(denominator, numerator); + } + + /** + * Gets the fraction as a {@code long}. This returns the whole number + * part of the fraction. + * + * @return the whole number fraction part + */ + @Override + public long longValue() { + return (long) numerator / denominator; + } + + /** + * Multiplies the value of this fraction by another, returning the + * result in reduced form. + * + * @param fraction the fraction to multiply by, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws NullPointerException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction multiplyBy(final Fraction fraction) { + Objects.requireNonNull(fraction, "fraction"); + if (numerator == 0 || fraction.numerator == 0) { + return ZERO; + } + // knuth 4.5.1 + // make sure we don't overflow unless the result *must* overflow. + final int d1 = greatestCommonDivisor(numerator, fraction.denominator); + final int d2 = greatestCommonDivisor(fraction.numerator, denominator); + return getReducedFraction(mulAndCheck(numerator / d1, fraction.numerator / d2), + mulPosAndCheck(denominator / d2, fraction.denominator / d1)); + } + + /** + * Gets a fraction that is the negative (-fraction) of this one. + * + *

The returned fraction is not reduced.

+ * + * @return a new fraction instance with the opposite signed numerator + */ + public Fraction negate() { + // the positive range is one smaller than the negative range of an int. + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: too large to negate"); + } + return new Fraction(-numerator, denominator); + } + + /** + * Gets a fraction that is raised to the passed in power. + * + *

The returned fraction is in reduced form.

+ * + * @param power the power to raise the fraction to + * @return {@code this} if the power is one, {@link #ONE} if the power + * is zero (even if the fraction equals ZERO) or a new fraction instance + * raised to the appropriate power + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction pow(final int power) { + if (power == 1) { + return this; + } + if (power == 0) { + return ONE; + } + if (power < 0) { + if (power == Integer.MIN_VALUE) { // MIN_VALUE can't be negated. + return this.invert().pow(2).pow(-(power / 2)); + } + return this.invert().pow(-power); + } + final Fraction f = this.multiplyBy(this); + if (power % 2 == 0) { // if even... + return f.pow(power / 2); + } + return f.pow(power / 2).multiplyBy(this); + } + + /** + * Reduce the fraction to the smallest values for the numerator and + * denominator, returning the result. + * + *

For example, if this fraction represents 2/4, then the result + * will be 1/2.

+ * + * @return a new reduced fraction instance, or this if no simplification possible + */ + public Fraction reduce() { + if (numerator == 0) { + return equals(ZERO) ? this : ZERO; + } + final int gcd = greatestCommonDivisor(Math.abs(numerator), denominator); + if (gcd == 1) { + return this; + } + return getFraction(numerator / gcd, denominator / gcd); + } + + /** + * Subtracts the value of another fraction from the value of this one, + * returning the result in reduced form. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws NullPointerException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an {@code int}. + */ + public Fraction subtract(final Fraction fraction) { + return addSub(fraction, false /* subtract */); } /** @@ -905,4 +890,19 @@ public final class Fraction extends Number implements Comparable { } return toProperString; } + + /** + * Gets the fraction as a {@link String}. + * + *

The format used is 'numerator/denominator' always. + * + * @return a {@link String} form of the fraction + */ + @Override + public String toString() { + if (toString == null) { + toString = getNumerator() + "/" + getDenominator(); + } + return toString; + } } diff --git a/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java b/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java index 04dd0e33a..c59a4dccd 100644 --- a/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java +++ b/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java @@ -30,116 +30,6 @@ import org.apache.commons.lang3.Validate; public class IEEE754rUtils { /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(double[]) to min(double...) - */ - public static double min(final double... array) { - Objects.requireNonNull(array, "array"); - Validate.isTrue(array.length != 0, "Array cannot be empty."); - - // Finds and returns min - double min = array[0]; - for (int i = 1; i < array.length; i++) { - min = min(array[i], min); - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(float[]) to min(float...) - */ - public static float min(final float... array) { - Objects.requireNonNull(array, "array"); - Validate.isTrue(array.length != 0, "Array cannot be empty."); - - // Finds and returns min - float min = array[0]; - for (int i = 1; i < array.length; i++) { - min = min(array[i], min); - } - - return min; - } - - /** - * Gets the minimum of three {@code double} values. - * - *

NaN is only returned if all numbers are NaN as per IEEE-754r.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static double min(final double a, final double b, final double c) { - return min(min(a, b), c); - } - - /** - * Gets the minimum of two {@code double} values. - * - *

NaN is only returned if all numbers are NaN as per IEEE-754r.

- * - * @param a value 1 - * @param b value 2 - * @return the smallest of the values - */ - public static double min(final double a, final double b) { - if (Double.isNaN(a)) { - return b; - } - if (Double.isNaN(b)) { - return a; - } - return Math.min(a, b); - } - - /** - * Gets the minimum of three {@code float} values. - * - *

NaN is only returned if all numbers are NaN as per IEEE-754r.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static float min(final float a, final float b, final float c) { - return min(min(a, b), c); - } - - /** - * Gets the minimum of two {@code float} values. - * - *

NaN is only returned if all numbers are NaN as per IEEE-754r.

- * - * @param a value 1 - * @param b value 2 - * @return the smallest of the values - */ - public static float min(final float a, final float b) { - if (Float.isNaN(a)) { - return b; - } - if (Float.isNaN(b)) { - return a; - } - return Math.min(a, b); - } - - /** * Returns the maximum value in an array. * * @param array an array, must not be null or empty @@ -161,6 +51,39 @@ public class IEEE754rUtils { return max; } + /** + * Gets the maximum of two {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static double max(final double a, final double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + return Math.max(a, b); + } + + /** + * Gets the maximum of three {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static double max(final double a, final double b, final double c) { + return max(max(a, b), c); + } + /** * Returns the maximum value in an array. * @@ -184,21 +107,7 @@ public class IEEE754rUtils { } /** - * Gets the maximum of three {@code double} values. - * - *

NaN is only returned if all numbers are NaN as per IEEE-754r.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - */ - public static double max(final double a, final double b, final double c) { - return max(max(a, b), c); - } - - /** - * Gets the maximum of two {@code double} values. + * Gets the maximum of two {@code float} values. * *

NaN is only returned if all numbers are NaN as per IEEE-754r.

* @@ -206,11 +115,11 @@ public class IEEE754rUtils { * @param b value 2 * @return the largest of the values */ - public static double max(final double a, final double b) { - if (Double.isNaN(a)) { + public static float max(final float a, final float b) { + if (Float.isNaN(a)) { return b; } - if (Double.isNaN(b)) { + if (Float.isNaN(b)) { return a; } return Math.max(a, b); @@ -231,22 +140,113 @@ public class IEEE754rUtils { } /** - * Gets the maximum of two {@code float} values. + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(double[]) to min(double...) + */ + public static double min(final double... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + * Gets the minimum of two {@code double} values. * *

NaN is only returned if all numbers are NaN as per IEEE-754r.

* * @param a value 1 * @param b value 2 - * @return the largest of the values + * @return the smallest of the values */ - public static float max(final float a, final float b) { + public static double min(final double a, final double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + return Math.min(a, b); + } + + /** + * Gets the minimum of three {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static double min(final double a, final double b, final double c) { + return min(min(a, b), c); + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(float[]) to min(float...) + */ + public static float min(final float... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + * Gets the minimum of two {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static float min(final float a, final float b) { if (Float.isNaN(a)) { return b; } if (Float.isNaN(b)) { return a; } - return Math.max(a, b); + return Math.min(a, b); + } + + /** + * Gets the minimum of three {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static float min(final float a, final float b, final float c) { + return min(min(a, b), c); } } diff --git a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java index 53c9fbe4f..53b260ffe 100644 --- a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java +++ b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java @@ -87,546 +87,201 @@ public class NumberUtils { /** - * {@link NumberUtils} instances should NOT be constructed in standard programming. - * Instead, the class should be used as {@code NumberUtils.toInt("6");}. + * Compares two {@code byte} values numerically. This is the same functionality as provided in Java 7. * - *

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

+ * @param x the first {@code byte} to compare + * @param y the second {@code byte} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 */ - public NumberUtils() { + public static int compare(final byte x, final byte y) { + return x - y; } /** - * Convert a {@link String} to an {@code int}, returning - * {@code zero} if the conversion fails. + * Compares two {@code int} values numerically. This is the same functionality as provided in Java 7. * - *

If the string is {@code null}, {@code zero} is returned.

- * - *
-     *   NumberUtils.toInt(null) = 0
-     *   NumberUtils.toInt("")   = 0
-     *   NumberUtils.toInt("1")  = 1
-     * 
- * - * @param str the string to convert, may be null - * @return the int represented by the string, or {@code zero} if - * conversion fails - * @since 2.1 + * @param x the first {@code int} to compare + * @param y the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 */ - public static int toInt(final String str) { - return toInt(str, 0); + public static int compare(final int x, final int y) { + if (x == y) { + return 0; + } + return x < y ? -1 : 1; } /** - * Convert a {@link String} to an {@code int}, returning a - * default value if the conversion fails. + * Compares to {@code long} values numerically. This is the same functionality as provided in Java 7. * - *

If the string is {@code null}, the default value is returned.

- * - *
-     *   NumberUtils.toInt(null, 1) = 1
-     *   NumberUtils.toInt("", 1)   = 1
-     *   NumberUtils.toInt("1", 0)  = 1
-     * 
- * - * @param str the string to convert, may be null - * @param defaultValue the default value - * @return the int represented by the string, or the default if conversion fails - * @since 2.1 + * @param x the first {@code long} to compare + * @param y the second {@code long} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 */ - public static int toInt(final String str, final int defaultValue) { + public static int compare(final long x, final long y) { + if (x == y) { + return 0; + } + return x < y ? -1 : 1; + } + + /** + * Compares to {@code short} values numerically. This is the same functionality as provided in Java 7. + * + * @param x the first {@code short} to compare + * @param y the second {@code short} to compare + * @return the value {@code 0} if {@code x == y}; + * a value less than {@code 0} if {@code x < y}; and + * a value greater than {@code 0} if {@code x > y} + * @since 3.4 + */ + public static int compare(final short x, final short y) { + if (x == y) { + return 0; + } + return x < y ? -1 : 1; + } + + /** + * Convert a {@link String} to a {@link BigDecimal}. + * + *

Returns {@code null} if the string is {@code null}.

+ * + * @param str a {@link String} to convert, may be null + * @return converted {@link BigDecimal} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static BigDecimal createBigDecimal(final String str) { if (str == null) { - return defaultValue; + return null; } - try { - return Integer.parseInt(str); - } catch (final NumberFormatException nfe) { - return defaultValue; + // handle JDK1.3.1 bug where "" throws IndexOutOfBoundsException + if (StringUtils.isBlank(str)) { + throw new NumberFormatException("A blank string is not a valid number"); } + return new BigDecimal(str); } /** - * Convert a {@link String} to a {@code long}, returning - * {@code zero} if the conversion fails. + * Convert a {@link String} to a {@link BigInteger}; + * since 3.2 it handles hexadecimal (0x or #) and octal (0) notations. * - *

If the string is {@code null}, {@code zero} is returned.

+ *

Returns {@code null} if the string is {@code null}.

* - *
-     *   NumberUtils.toLong(null) = 0L
-     *   NumberUtils.toLong("")   = 0L
-     *   NumberUtils.toLong("1")  = 1L
-     * 
- * - * @param str the string to convert, may be null - * @return the long represented by the string, or {@code 0} if - * conversion fails - * @since 2.1 + * @param str a {@link String} to convert, may be null + * @return converted {@link BigInteger} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted */ - public static long toLong(final String str) { - return toLong(str, 0L); - } - - /** - * Convert a {@link String} to a {@code long}, returning a - * default value if the conversion fails. - * - *

If the string is {@code null}, the default value is returned.

- * - *
-     *   NumberUtils.toLong(null, 1L) = 1L
-     *   NumberUtils.toLong("", 1L)   = 1L
-     *   NumberUtils.toLong("1", 0L)  = 1L
-     * 
- * - * @param str the string to convert, may be null - * @param defaultValue the default value - * @return the long represented by the string, or the default if conversion fails - * @since 2.1 - */ - public static long toLong(final String str, final long defaultValue) { + public static BigInteger createBigInteger(final String str) { if (str == null) { - return defaultValue; + return null; } - try { - return Long.parseLong(str); - } catch (final NumberFormatException nfe) { - return defaultValue; + if (str.isEmpty()) { + throw new NumberFormatException("An empty string is not a valid number"); } + int pos = 0; // offset within string + int radix = 10; + boolean negate = false; // need to negate later? + final char char0 = str.charAt(0); + if (char0 == '-') { + negate = true; + pos = 1; + } else if (char0 == '+') { + pos = 1; + } + if (str.startsWith("0x", pos) || str.startsWith("0X", pos)) { // hex + radix = 16; + pos += 2; + } else if (str.startsWith("#", pos)) { // alternative hex (allowed by Long/Integer) + radix = 16; + pos++; + } else if (str.startsWith("0", pos) && str.length() > pos + 1) { // octal; so long as there are additional digits + radix = 8; + pos++; + } // default is to treat as decimal + + final BigInteger value = new BigInteger(str.substring(pos), radix); + return negate ? value.negate() : value; } /** - * Convert a {@link String} to a {@code float}, returning - * {@code 0.0f} if the conversion fails. + * Convert a {@link String} to a {@link Double}. * - *

If the string {@code str} is {@code null}, - * {@code 0.0f} is returned.

+ *

Returns {@code null} if the string is {@code null}.

* - *
-     *   NumberUtils.toFloat(null)   = 0.0f
-     *   NumberUtils.toFloat("")     = 0.0f
-     *   NumberUtils.toFloat("1.5")  = 1.5f
-     * 
- * - * @param str the string to convert, may be {@code null} - * @return the float represented by the string, or {@code 0.0f} - * if conversion fails - * @since 2.1 + * @param str a {@link String} to convert, may be null + * @return converted {@link Double} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted */ - public static float toFloat(final String str) { - return toFloat(str, 0.0f); - } - - /** - * Convert a {@link String} to a {@code float}, returning a - * default value if the conversion fails. - * - *

If the string {@code str} is {@code null}, the default - * value is returned.

- * - *
-     *   NumberUtils.toFloat(null, 1.1f)   = 1.0f
-     *   NumberUtils.toFloat("", 1.1f)     = 1.1f
-     *   NumberUtils.toFloat("1.5", 0.0f)  = 1.5f
-     * 
- * - * @param str the string to convert, may be {@code null} - * @param defaultValue the default value - * @return the float represented by the string, or defaultValue - * if conversion fails - * @since 2.1 - */ - public static float toFloat(final String str, final float defaultValue) { - if (str == null) { - return defaultValue; - } - try { - return Float.parseFloat(str); - } catch (final NumberFormatException nfe) { - return defaultValue; - } - } - - /** - * Convert a {@link String} to a {@code double}, returning - * {@code 0.0d} if the conversion fails. - * - *

If the string {@code str} is {@code null}, - * {@code 0.0d} is returned.

- * - *
-     *   NumberUtils.toDouble(null)   = 0.0d
-     *   NumberUtils.toDouble("")     = 0.0d
-     *   NumberUtils.toDouble("1.5")  = 1.5d
-     * 
- * - * @param str the string to convert, may be {@code null} - * @return the double represented by the string, or {@code 0.0d} - * if conversion fails - * @since 2.1 - */ - public static double toDouble(final String str) { - return toDouble(str, 0.0d); - } - - /** - * Convert a {@link String} to a {@code double}, returning a - * default value if the conversion fails. - * - *

If the string {@code str} is {@code null}, the default - * value is returned.

- * - *
-     *   NumberUtils.toDouble(null, 1.1d)   = 1.1d
-     *   NumberUtils.toDouble("", 1.1d)     = 1.1d
-     *   NumberUtils.toDouble("1.5", 0.0d)  = 1.5d
-     * 
- * - * @param str the string to convert, may be {@code null} - * @param defaultValue the default value - * @return the double represented by the string, or defaultValue - * if conversion fails - * @since 2.1 - */ - public static double toDouble(final String str, final double defaultValue) { - if (str == null) { - return defaultValue; - } - try { - return Double.parseDouble(str); - } catch (final NumberFormatException nfe) { - return defaultValue; - } - } - - /** - * Convert a {@link BigDecimal} to a {@code double}. - * - *

If the {@link BigDecimal} {@code value} is - * {@code null}, then the specified default value is returned.

- * - *
-     *   NumberUtils.toDouble(null)                     = 0.0d
-     *   NumberUtils.toDouble(BigDecimal.valueOf(8.5d)) = 8.5d
-     * 
- * - * @param value the {@link BigDecimal} to convert, may be {@code null}. - * @return the double represented by the {@link BigDecimal} or - * {@code 0.0d} if the {@link BigDecimal} is {@code null}. - * @since 3.8 - */ - public static double toDouble(final BigDecimal value) { - return toDouble(value, 0.0d); - } - - /** - * Convert a {@link BigDecimal} to a {@code double}. - * - *

If the {@link BigDecimal} {@code value} is - * {@code null}, then the specified default value is returned.

- * - *
-     *   NumberUtils.toDouble(null, 1.1d)                     = 1.1d
-     *   NumberUtils.toDouble(BigDecimal.valueOf(8.5d), 1.1d) = 8.5d
-     * 
- * - * @param value the {@link BigDecimal} to convert, may be {@code null}. - * @param defaultValue the default value - * @return the double represented by the {@link BigDecimal} or the - * defaultValue if the {@link BigDecimal} is {@code null}. - * @since 3.8 - */ - public static double toDouble(final BigDecimal value, final double defaultValue) { - return value == null ? defaultValue : value.doubleValue(); - } - - /** - * Convert a {@link String} to a {@code byte}, returning - * {@code zero} if the conversion fails. - * - *

If the string is {@code null}, {@code zero} is returned.

- * - *
-     *   NumberUtils.toByte(null) = 0
-     *   NumberUtils.toByte("")   = 0
-     *   NumberUtils.toByte("1")  = 1
-     * 
- * - * @param str the string to convert, may be null - * @return the byte represented by the string, or {@code zero} if - * conversion fails - * @since 2.5 - */ - public static byte toByte(final String str) { - return toByte(str, (byte) 0); - } - - /** - * Convert a {@link String} to a {@code byte}, returning a - * default value if the conversion fails. - * - *

If the string is {@code null}, the default value is returned.

- * - *
-     *   NumberUtils.toByte(null, 1) = 1
-     *   NumberUtils.toByte("", 1)   = 1
-     *   NumberUtils.toByte("1", 0)  = 1
-     * 
- * - * @param str the string to convert, may be null - * @param defaultValue the default value - * @return the byte represented by the string, or the default if conversion fails - * @since 2.5 - */ - public static byte toByte(final String str, final byte defaultValue) { + public static Double createDouble(final String str) { if (str == null) { - return defaultValue; - } - try { - return Byte.parseByte(str); - } catch (final NumberFormatException nfe) { - return defaultValue; + return null; } + return Double.valueOf(str); } /** - * Convert a {@link String} to a {@code short}, returning - * {@code zero} if the conversion fails. + * Convert a {@link String} to a {@link Float}. * - *

If the string is {@code null}, {@code zero} is returned.

+ *

Returns {@code null} if the string is {@code null}.

* - *
-     *   NumberUtils.toShort(null) = 0
-     *   NumberUtils.toShort("")   = 0
-     *   NumberUtils.toShort("1")  = 1
-     * 
- * - * @param str the string to convert, may be null - * @return the short represented by the string, or {@code zero} if - * conversion fails - * @since 2.5 + * @param str a {@link String} to convert, may be null + * @return converted {@link Float} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted */ - public static short toShort(final String str) { - return toShort(str, (short) 0); - } - - /** - * Convert a {@link String} to an {@code short}, returning a - * default value if the conversion fails. - * - *

If the string is {@code null}, the default value is returned.

- * - *
-     *   NumberUtils.toShort(null, 1) = 1
-     *   NumberUtils.toShort("", 1)   = 1
-     *   NumberUtils.toShort("1", 0)  = 1
-     * 
- * - * @param str the string to convert, may be null - * @param defaultValue the default value - * @return the short represented by the string, or the default if conversion fails - * @since 2.5 - */ - public static short toShort(final String str, final short defaultValue) { + public static Float createFloat(final String str) { if (str == null) { - return defaultValue; + return null; } - try { - return Short.parseShort(str); - } catch (final NumberFormatException nfe) { - return defaultValue; + return Float.valueOf(str); + } + + /** + * Convert a {@link String} to a {@link Integer}, handling + * hexadecimal (0xhhhh) and octal (0dddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed. + * + *

Returns {@code null} if the string is {@code null}.

+ * + * @param str a {@link String} to convert, may be null + * @return converted {@link Integer} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted + */ + public static Integer createInteger(final String str) { + if (str == null) { + return null; } + // decode() handles 0xAABD and 0777 (hex and octal) as well. + return Integer.decode(str); } /** - * Convert a {@link BigDecimal} to a {@link BigDecimal} with a scale of - * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied - * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * Convert a {@link String} to a {@link Long}; + * since 3.1 it handles hexadecimal (0Xhhhh) and octal (0ddd) notations. + * N.B. a leading zero means octal; spaces are not trimmed. * - *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the - * decimal point.

+ *

Returns {@code null} if the string is {@code null}.

* - * @param value the {@link BigDecimal} to convert, may be null. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 + * @param str a {@link String} to convert, may be null + * @return converted {@link Long} (or null if the input is null) + * @throws NumberFormatException if the value cannot be converted */ - public static BigDecimal toScaledBigDecimal(final BigDecimal value) { - return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); - } - - /** - * Convert a {@link BigDecimal} to a {@link BigDecimal} whose scale is the - * specified value with a {@link RoundingMode} applied. If the input {@code value} - * is {@code null}, we simply return {@code BigDecimal.ZERO}. - * - * @param value the {@link BigDecimal} to convert, may be null. - * @param scale the number of digits to the right of the decimal point. - * @param roundingMode a rounding behavior for numerical operations capable of - * discarding precision. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final BigDecimal value, final int scale, final RoundingMode roundingMode) { - if (value == null) { - return BigDecimal.ZERO; + public static Long createLong(final String str) { + if (str == null) { + return null; } - return value.setScale( - scale, - roundingMode == null ? RoundingMode.HALF_EVEN : roundingMode - ); + return Long.decode(str); } - /** - * Convert a {@link Float} to a {@link BigDecimal} with a scale of - * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied - * {@code value} is null, then {@code BigDecimal.ZERO} is returned. - * - *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the - * decimal point.

- * - * @param value the {@link Float} to convert, may be null. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final Float value) { - return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); - } - - /** - * Convert a {@link Float} to a {@link BigDecimal} whose scale is the - * specified value with a {@link RoundingMode} applied. If the input {@code value} - * is {@code null}, we simply return {@code BigDecimal.ZERO}. - * - * @param value the {@link Float} to convert, may be null. - * @param scale the number of digits to the right of the decimal point. - * @param roundingMode a rounding behavior for numerical operations capable of - * discarding precision. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final Float value, final int scale, final RoundingMode roundingMode) { - if (value == null) { - return BigDecimal.ZERO; - } - return toScaledBigDecimal( - BigDecimal.valueOf(value), - scale, - roundingMode - ); - } - - /** - * Convert a {@link Double} to a {@link BigDecimal} with a scale of - * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied - * {@code value} is null, then {@code BigDecimal.ZERO} is returned. - * - *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the - * decimal point.

- * - * @param value the {@link Double} to convert, may be null. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final Double value) { - return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); - } - - /** - * Convert a {@link Double} to a {@link BigDecimal} whose scale is the - * specified value with a {@link RoundingMode} applied. If the input {@code value} - * is {@code null}, we simply return {@code BigDecimal.ZERO}. - * - * @param value the {@link Double} to convert, may be null. - * @param scale the number of digits to the right of the decimal point. - * @param roundingMode a rounding behavior for numerical operations capable of - * discarding precision. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final Double value, final int scale, final RoundingMode roundingMode) { - if (value == null) { - return BigDecimal.ZERO; - } - return toScaledBigDecimal( - BigDecimal.valueOf(value), - scale, - roundingMode - ); - } - - /** - * Convert a {@link String} to a {@link BigDecimal} with a scale of - * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied - * {@code value} is null, then {@code BigDecimal.ZERO} is returned. - * - *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the - * decimal point.

- * - * @param value the {@link String} to convert, may be null. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final String value) { - return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); - } - - /** - * Convert a {@link String} to a {@link BigDecimal} whose scale is the - * specified value with a {@link RoundingMode} applied. If the input {@code value} - * is {@code null}, we simply return {@code BigDecimal.ZERO}. - * - * @param value the {@link String} to convert, may be null. - * @param scale the number of digits to the right of the decimal point. - * @param roundingMode a rounding behavior for numerical operations capable of - * discarding precision. - * @return the scaled, with appropriate rounding, {@link BigDecimal}. - * @since 3.8 - */ - public static BigDecimal toScaledBigDecimal(final String value, final int scale, final RoundingMode roundingMode) { - if (value == null) { - return BigDecimal.ZERO; - } - return toScaledBigDecimal( - createBigDecimal(value), - scale, - roundingMode - ); - } - - // must handle Long, Float, Integer, Float, Short, - // BigDecimal, BigInteger and Byte - // useful methods: - // Byte.decode(String) - // Byte.valueOf(String, int radix) - // Byte.valueOf(String) - // Double.valueOf(String) - // Float.valueOf(String) - // Float.valueOf(String) - // Integer.valueOf(String, int radix) - // Integer.valueOf(String) - // Integer.decode(String) - // Integer.getInteger(String) - // Integer.getInteger(String, int val) - // Integer.getInteger(String, Integer val) - // Integer.valueOf(String) - // Double.valueOf(String) - // new Byte(String) - // Long.valueOf(String) - // Long.getLong(String) - // Long.getLong(String, int) - // Long.getLong(String, Integer) - // Long.valueOf(String, int) - // Long.valueOf(String) - // Short.valueOf(String) - // Short.decode(String) - // Short.valueOf(String, int) - // Short.valueOf(String) - // new BigDecimal(String) - // new BigInteger(String) - // new BigInteger(String, int radix) - // Possible inputs: - // 45 45.5 45E7 4.5E7 Hex Oct Binary xxxF xxxD xxxf xxxd - // plus minus everything. Prolly more. A lot are not separable. - /** * Turns a string value into a java.lang.Number. * @@ -830,7 +485,7 @@ public class NumberUtils { return createBigDecimal(str); } - /** + /** * Utility method for {@link #createNumber(java.lang.String)}. * *

Returns mantissa of the given number.

@@ -846,36 +501,6 @@ public class NumberUtils { return hasSign ? str.substring(1, stopPos) : str.substring(0, stopPos); } - /** - * Utility method for {@link #createNumber(java.lang.String)}. - * - *

This will check if the magnitude of the number is zero by checking if there - * are only zeros before and after the decimal place.

- * - *

Note: It is assumed that the input string has been converted - * to either a Float or Double with a value of zero when this method is called. - * This eliminates invalid input for example {@code ".", ".D", ".e0"}.

- * - *

Thus the method only requires checking if both arguments are null, empty or - * contain only zeros.

- * - *

Given {@code s = mant + "." + dec}:

- *
    - *
  • {@code true} if s is {@code "0.0"} - *
  • {@code true} if s is {@code "0."} - *
  • {@code true} if s is {@code ".0"} - *
  • {@code false} otherwise (this assumes {@code "."} is not possible) - *
- * - * @param mant the mantissa decimal digits before the decimal point (sign must be removed; never null) - * @param dec the decimal digits after the decimal point (exponent and type specifier removed; - * can be null) - * @return true if the magnitude is zero - */ - private static boolean isZero(final String mant, final String dec) { - return isAllZeros(mant) && isAllZeros(dec); - } - /** * Utility method for {@link #createNumber(java.lang.String)}. * @@ -896,707 +521,6 @@ public class NumberUtils { return true; } - /** - * Convert a {@link String} to a {@link Float}. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link Float} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static Float createFloat(final String str) { - if (str == null) { - return null; - } - return Float.valueOf(str); - } - - /** - * Convert a {@link String} to a {@link Double}. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link Double} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static Double createDouble(final String str) { - if (str == null) { - return null; - } - return Double.valueOf(str); - } - - /** - * Convert a {@link String} to a {@link Integer}, handling - * hexadecimal (0xhhhh) and octal (0dddd) notations. - * N.B. a leading zero means octal; spaces are not trimmed. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link Integer} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static Integer createInteger(final String str) { - if (str == null) { - return null; - } - // decode() handles 0xAABD and 0777 (hex and octal) as well. - return Integer.decode(str); - } - - /** - * Convert a {@link String} to a {@link Long}; - * since 3.1 it handles hexadecimal (0Xhhhh) and octal (0ddd) notations. - * N.B. a leading zero means octal; spaces are not trimmed. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link Long} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static Long createLong(final String str) { - if (str == null) { - return null; - } - return Long.decode(str); - } - - /** - * Convert a {@link String} to a {@link BigInteger}; - * since 3.2 it handles hexadecimal (0x or #) and octal (0) notations. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link BigInteger} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static BigInteger createBigInteger(final String str) { - if (str == null) { - return null; - } - if (str.isEmpty()) { - throw new NumberFormatException("An empty string is not a valid number"); - } - int pos = 0; // offset within string - int radix = 10; - boolean negate = false; // need to negate later? - final char char0 = str.charAt(0); - if (char0 == '-') { - negate = true; - pos = 1; - } else if (char0 == '+') { - pos = 1; - } - if (str.startsWith("0x", pos) || str.startsWith("0X", pos)) { // hex - radix = 16; - pos += 2; - } else if (str.startsWith("#", pos)) { // alternative hex (allowed by Long/Integer) - radix = 16; - pos++; - } else if (str.startsWith("0", pos) && str.length() > pos + 1) { // octal; so long as there are additional digits - radix = 8; - pos++; - } // default is to treat as decimal - - final BigInteger value = new BigInteger(str.substring(pos), radix); - return negate ? value.negate() : value; - } - - /** - * Convert a {@link String} to a {@link BigDecimal}. - * - *

Returns {@code null} if the string is {@code null}.

- * - * @param str a {@link String} to convert, may be null - * @return converted {@link BigDecimal} (or null if the input is null) - * @throws NumberFormatException if the value cannot be converted - */ - public static BigDecimal createBigDecimal(final String str) { - if (str == null) { - return null; - } - // handle JDK1.3.1 bug where "" throws IndexOutOfBoundsException - if (StringUtils.isBlank(str)) { - throw new NumberFormatException("A blank string is not a valid number"); - } - return new BigDecimal(str); - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(long[]) to min(long...) - */ - public static long min(final long... array) { - // Validates input - validateArray(array); - - // Finds and returns min - long min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(int[]) to min(int...) - */ - public static int min(final int... array) { - // Validates input - validateArray(array); - - // Finds and returns min - int min = array[0]; - for (int j = 1; j < array.length; j++) { - if (array[j] < min) { - min = array[j]; - } - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(short[]) to min(short...) - */ - public static short min(final short... array) { - // Validates input - validateArray(array); - - // Finds and returns min - short min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from min(byte[]) to min(byte...) - */ - public static byte min(final byte... array) { - // Validates input - validateArray(array); - - // Finds and returns min - byte min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @see IEEE754rUtils#min(double[]) IEEE754rUtils for a version of this method that handles NaN differently - * @since 3.4 Changed signature from min(double[]) to min(double...) - */ - public static double min(final double... array) { - // Validates input - validateArray(array); - - // Finds and returns min - double min = array[0]; - for (int i = 1; i < array.length; i++) { - if (Double.isNaN(array[i])) { - return Double.NaN; - } - if (array[i] < min) { - min = array[i]; - } - } - - return min; - } - - /** - * Returns the minimum value in an array. - * - * @param array an array, must not be null or empty - * @return the minimum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @see IEEE754rUtils#min(float[]) IEEE754rUtils for a version of this method that handles NaN differently - * @since 3.4 Changed signature from min(float[]) to min(float...) - */ - public static float min(final float... array) { - // Validates input - validateArray(array); - - // Finds and returns min - float min = array[0]; - for (int i = 1; i < array.length; i++) { - if (Float.isNaN(array[i])) { - return Float.NaN; - } - if (array[i] < min) { - min = array[i]; - } - } - - return min; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from max(long[]) to max(long...) - */ - public static long max(final long... array) { - // Validates input - validateArray(array); - - // Finds and returns max - long max = array[0]; - for (int j = 1; j < array.length; j++) { - if (array[j] > max) { - max = array[j]; - } - } - - return max; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from max(int[]) to max(int...) - */ - public static int max(final int... array) { - // Validates input - validateArray(array); - - // Finds and returns max - int max = array[0]; - for (int j = 1; j < array.length; j++) { - if (array[j] > max) { - max = array[j]; - } - } - - return max; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from max(short[]) to max(short...) - */ - public static short max(final short... array) { - // Validates input - validateArray(array); - - // Finds and returns max - short max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - - return max; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @since 3.4 Changed signature from max(byte[]) to max(byte...) - */ - public static byte max(final byte... array) { - // Validates input - validateArray(array); - - // Finds and returns max - byte max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - - return max; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @see IEEE754rUtils#max(double[]) IEEE754rUtils for a version of this method that handles NaN differently - * @since 3.4 Changed signature from max(double[]) to max(double...) - */ - public static double max(final double... array) { - // Validates input - validateArray(array); - - // Finds and returns max - double max = array[0]; - for (int j = 1; j < array.length; j++) { - if (Double.isNaN(array[j])) { - return Double.NaN; - } - if (array[j] > max) { - max = array[j]; - } - } - - return max; - } - - /** - * Returns the maximum value in an array. - * - * @param array an array, must not be null or empty - * @return the maximum value in the array - * @throws NullPointerException if {@code array} is {@code null} - * @throws IllegalArgumentException if {@code array} is empty - * @see IEEE754rUtils#max(float[]) IEEE754rUtils for a version of this method that handles NaN differently - * @since 3.4 Changed signature from max(float[]) to max(float...) - */ - public static float max(final float... array) { - // Validates input - validateArray(array); - - // Finds and returns max - float max = array[0]; - for (int j = 1; j < array.length; j++) { - if (Float.isNaN(array[j])) { - return Float.NaN; - } - if (array[j] > max) { - max = array[j]; - } - } - - return max; - } - - /** - * Checks if the specified array is neither null nor empty. - * - * @param array the array to check - * @throws IllegalArgumentException if {@code array} is empty - * @throws NullPointerException if {@code array} is {@code null} - */ - private static void validateArray(final Object array) { - Objects.requireNonNull(array, "array"); - Validate.isTrue(Array.getLength(array) != 0, "Array cannot be empty."); - } - - // 3 param min - /** - * Gets the minimum of three {@code long} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static long min(long a, final long b, final long c) { - if (b < a) { - a = b; - } - if (c < a) { - a = c; - } - return a; - } - - /** - * Gets the minimum of three {@code int} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static int min(int a, final int b, final int c) { - if (b < a) { - a = b; - } - if (c < a) { - a = c; - } - return a; - } - - /** - * Gets the minimum of three {@code short} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static short min(short a, final short b, final short c) { - if (b < a) { - a = b; - } - if (c < a) { - a = c; - } - return a; - } - - /** - * Gets the minimum of three {@code byte} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - */ - public static byte min(byte a, final byte b, final byte c) { - if (b < a) { - a = b; - } - if (c < a) { - a = c; - } - return a; - } - - /** - * Gets the minimum of three {@code double} values. - * - *

If any value is {@code NaN}, {@code NaN} is - * returned. Infinity is handled.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - * @see IEEE754rUtils#min(double, double, double) for a version of this method that handles NaN differently - */ - public static double min(final double a, final double b, final double c) { - return Math.min(Math.min(a, b), c); - } - - /** - * Gets the minimum of three {@code float} values. - * - *

If any value is {@code NaN}, {@code NaN} is - * returned. Infinity is handled.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values - * @see IEEE754rUtils#min(float, float, float) for a version of this method that handles NaN differently - */ - public static float min(final float a, final float b, final float c) { - return Math.min(Math.min(a, b), c); - } - - // 3 param max - /** - * Gets the maximum of three {@code long} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - */ - public static long max(long a, final long b, final long c) { - if (b > a) { - a = b; - } - if (c > a) { - a = c; - } - return a; - } - - /** - * Gets the maximum of three {@code int} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - */ - public static int max(int a, final int b, final int c) { - if (b > a) { - a = b; - } - if (c > a) { - a = c; - } - return a; - } - - /** - * Gets the maximum of three {@code short} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - */ - public static short max(short a, final short b, final short c) { - if (b > a) { - a = b; - } - if (c > a) { - a = c; - } - return a; - } - - /** - * Gets the maximum of three {@code byte} values. - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - */ - public static byte max(byte a, final byte b, final byte c) { - if (b > a) { - a = b; - } - if (c > a) { - a = c; - } - return a; - } - - /** - * Gets the maximum of three {@code double} values. - * - *

If any value is {@code NaN}, {@code NaN} is - * returned. Infinity is handled.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - * @see IEEE754rUtils#max(double, double, double) for a version of this method that handles NaN differently - */ - public static double max(final double a, final double b, final double c) { - return Math.max(Math.max(a, b), c); - } - - /** - * Gets the maximum of three {@code float} values. - * - *

If any value is {@code NaN}, {@code NaN} is - * returned. Infinity is handled.

- * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values - * @see IEEE754rUtils#max(float, float, float) for a version of this method that handles NaN differently - */ - public static float max(final float a, final float b, final float c) { - return Math.max(Math.max(a, b), c); - } - - /** - * Checks whether the {@link String} contains only - * digit characters. - * - *

{@code null} and empty String will return - * {@code false}.

- * - * @param str the {@link String} to check - * @return {@code true} if str contains only Unicode numeric - */ - public static boolean isDigits(final String str) { - return StringUtils.isNumeric(str); - } - - /** - * Checks whether the String is a valid Java number. - * - *

Valid numbers include hexadecimal marked with the {@code 0x} or - * {@code 0X} qualifier, octal numbers, scientific notation and - * numbers marked with a type qualifier (e.g. 123L).

- * - *

Non-hexadecimal strings beginning with a leading zero are - * treated as octal values. Thus the string {@code 09} will return - * {@code false}, since {@code 9} is not a valid octal value. - * However, numbers beginning with {@code 0.} are treated as decimal.

- * - *

{@code null} and empty/blank {@link String} will return - * {@code false}.

- * - *

Note, {@link #createNumber(String)} should return a number for every - * input resulting in {@code true}.

- * - * @param str the {@link String} to check - * @return {@code true} if the string is a correctly formatted number - * @since 3.3 the code supports hexadecimal {@code 0Xhhh} an - * octal {@code 0ddd} validation - * @deprecated This feature will be removed in Lang 4, - * use {@link NumberUtils#isCreatable(String)} instead - */ - @Deprecated - public static boolean isNumber(final String str) { - return isCreatable(str); - } - /** * Checks whether the String is a valid Java number. * @@ -1733,6 +657,50 @@ public class NumberUtils { return !allowSigns && foundDigit; } + /** + * Checks whether the {@link String} contains only + * digit characters. + * + *

{@code null} and empty String will return + * {@code false}.

+ * + * @param str the {@link String} to check + * @return {@code true} if str contains only Unicode numeric + */ + public static boolean isDigits(final String str) { + return StringUtils.isNumeric(str); + } + + /** + * Checks whether the String is a valid Java number. + * + *

Valid numbers include hexadecimal marked with the {@code 0x} or + * {@code 0X} qualifier, octal numbers, scientific notation and + * numbers marked with a type qualifier (e.g. 123L).

+ * + *

Non-hexadecimal strings beginning with a leading zero are + * treated as octal values. Thus the string {@code 09} will return + * {@code false}, since {@code 9} is not a valid octal value. + * However, numbers beginning with {@code 0.} are treated as decimal.

+ * + *

{@code null} and empty/blank {@link String} will return + * {@code false}.

+ * + *

Note, {@link #createNumber(String)} should return a number for every + * input resulting in {@code true}.

+ * + * @param str the {@link String} to check + * @return {@code true} if the string is a correctly formatted number + * @since 3.3 the code supports hexadecimal {@code 0Xhhh} an + * octal {@code 0ddd} validation + * @deprecated This feature will be removed in Lang 4, + * use {@link NumberUtils#isCreatable(String)} instead + */ + @Deprecated + public static boolean isNumber(final String str) { + return isCreatable(str); + } + /** * Checks whether the given String is a parsable number. * @@ -1766,6 +734,1093 @@ public class NumberUtils { return withDecimalsParsing(str, 0); } + /** + * Utility method for {@link #createNumber(java.lang.String)}. + * + *

This will check if the magnitude of the number is zero by checking if there + * are only zeros before and after the decimal place.

+ * + *

Note: It is assumed that the input string has been converted + * to either a Float or Double with a value of zero when this method is called. + * This eliminates invalid input for example {@code ".", ".D", ".e0"}.

+ * + *

Thus the method only requires checking if both arguments are null, empty or + * contain only zeros.

+ * + *

Given {@code s = mant + "." + dec}:

+ *
    + *
  • {@code true} if s is {@code "0.0"} + *
  • {@code true} if s is {@code "0."} + *
  • {@code true} if s is {@code ".0"} + *
  • {@code false} otherwise (this assumes {@code "."} is not possible) + *
+ * + * @param mant the mantissa decimal digits before the decimal point (sign must be removed; never null) + * @param dec the decimal digits after the decimal point (exponent and type specifier removed; + * can be null) + * @return true if the magnitude is zero + */ + private static boolean isZero(final String mant, final String dec) { + return isAllZeros(mant) && isAllZeros(dec); + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(byte[]) to max(byte...) + */ + public static byte max(final byte... array) { + // Validates input + validateArray(array); + + // Finds and returns max + byte max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + + return max; + } + + /** + * Gets the maximum of three {@code byte} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static byte max(byte a, final byte b, final byte c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @see IEEE754rUtils#max(double[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from max(double[]) to max(double...) + */ + public static double max(final double... array) { + // Validates input + validateArray(array); + + // Finds and returns max + double max = array[0]; + for (int j = 1; j < array.length; j++) { + if (Double.isNaN(array[j])) { + return Double.NaN; + } + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + * Gets the maximum of three {@code double} values. + * + *

If any value is {@code NaN}, {@code NaN} is + * returned. Infinity is handled.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + * @see IEEE754rUtils#max(double, double, double) for a version of this method that handles NaN differently + */ + public static double max(final double a, final double b, final double c) { + return Math.max(Math.max(a, b), c); + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @see IEEE754rUtils#max(float[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from max(float[]) to max(float...) + */ + public static float max(final float... array) { + // Validates input + validateArray(array); + + // Finds and returns max + float max = array[0]; + for (int j = 1; j < array.length; j++) { + if (Float.isNaN(array[j])) { + return Float.NaN; + } + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + // must handle Long, Float, Integer, Float, Short, + // BigDecimal, BigInteger and Byte + // useful methods: + // Byte.decode(String) + // Byte.valueOf(String, int radix) + // Byte.valueOf(String) + // Double.valueOf(String) + // Float.valueOf(String) + // Float.valueOf(String) + // Integer.valueOf(String, int radix) + // Integer.valueOf(String) + // Integer.decode(String) + // Integer.getInteger(String) + // Integer.getInteger(String, int val) + // Integer.getInteger(String, Integer val) + // Integer.valueOf(String) + // Double.valueOf(String) + // new Byte(String) + // Long.valueOf(String) + // Long.getLong(String) + // Long.getLong(String, int) + // Long.getLong(String, Integer) + // Long.valueOf(String, int) + // Long.valueOf(String) + // Short.valueOf(String) + // Short.decode(String) + // Short.valueOf(String, int) + // Short.valueOf(String) + // new BigDecimal(String) + // new BigInteger(String) + // new BigInteger(String, int radix) + // Possible inputs: + // 45 45.5 45E7 4.5E7 Hex Oct Binary xxxF xxxD xxxf xxxd + // plus minus everything. Prolly more. A lot are not separable. + + /** + * Gets the maximum of three {@code float} values. + * + *

If any value is {@code NaN}, {@code NaN} is + * returned. Infinity is handled.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + * @see IEEE754rUtils#max(float, float, float) for a version of this method that handles NaN differently + */ + public static float max(final float a, final float b, final float c) { + return Math.max(Math.max(a, b), c); + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(int[]) to max(int...) + */ + public static int max(final int... array) { + // Validates input + validateArray(array); + + // Finds and returns max + int max = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + /** + * Gets the maximum of three {@code int} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static int max(int a, final int b, final int c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(long[]) to max(long...) + */ + public static long max(final long... array) { + // Validates input + validateArray(array); + + // Finds and returns max + long max = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] > max) { + max = array[j]; + } + } + + return max; + } + + // 3 param max + /** + * Gets the maximum of three {@code long} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static long max(long a, final long b, final long c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the maximum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(short[]) to max(short...) + */ + public static short max(final short... array) { + // Validates input + validateArray(array); + + // Finds and returns max + short max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + + return max; + } + + /** + * Gets the maximum of three {@code short} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static short max(short a, final short b, final short c) { + if (b > a) { + a = b; + } + if (c > a) { + a = c; + } + return a; + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(byte[]) to min(byte...) + */ + public static byte min(final byte... array) { + // Validates input + validateArray(array); + + // Finds and returns min + byte min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + * Gets the minimum of three {@code byte} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static byte min(byte a, final byte b, final byte c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @see IEEE754rUtils#min(double[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from min(double[]) to min(double...) + */ + public static double min(final double... array) { + // Validates input + validateArray(array); + + // Finds and returns min + double min = array[0]; + for (int i = 1; i < array.length; i++) { + if (Double.isNaN(array[i])) { + return Double.NaN; + } + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + * Gets the minimum of three {@code double} values. + * + *

If any value is {@code NaN}, {@code NaN} is + * returned. Infinity is handled.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + * @see IEEE754rUtils#min(double, double, double) for a version of this method that handles NaN differently + */ + public static double min(final double a, final double b, final double c) { + return Math.min(Math.min(a, b), c); + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @see IEEE754rUtils#min(float[]) IEEE754rUtils for a version of this method that handles NaN differently + * @since 3.4 Changed signature from min(float[]) to min(float...) + */ + public static float min(final float... array) { + // Validates input + validateArray(array); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + if (Float.isNaN(array[i])) { + return Float.NaN; + } + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + * Gets the minimum of three {@code float} values. + * + *

If any value is {@code NaN}, {@code NaN} is + * returned. Infinity is handled.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + * @see IEEE754rUtils#min(float, float, float) for a version of this method that handles NaN differently + */ + public static float min(final float a, final float b, final float c) { + return Math.min(Math.min(a, b), c); + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(int[]) to min(int...) + */ + public static int min(final int... array) { + // Validates input + validateArray(array); + + // Finds and returns min + int min = array[0]; + for (int j = 1; j < array.length; j++) { + if (array[j] < min) { + min = array[j]; + } + } + + return min; + } + + /** + * Gets the minimum of three {@code int} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static int min(int a, final int b, final int c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(long[]) to min(long...) + */ + public static long min(final long... array) { + // Validates input + validateArray(array); + + // Finds and returns min + long min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + // 3 param min + /** + * Gets the minimum of three {@code long} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static long min(long a, final long b, final long c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(short[]) to min(short...) + */ + public static short min(final short... array) { + // Validates input + validateArray(array); + + // Finds and returns min + short min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + + return min; + } + + /** + * Gets the minimum of three {@code short} values. + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static short min(short a, final short b, final short c) { + if (b < a) { + a = b; + } + if (c < a) { + a = c; + } + return a; + } + + /** + * Convert a {@link String} to a {@code byte}, returning + * {@code zero} if the conversion fails. + * + *

If the string is {@code null}, {@code zero} is returned.

+ * + *
+     *   NumberUtils.toByte(null) = 0
+     *   NumberUtils.toByte("")   = 0
+     *   NumberUtils.toByte("1")  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @return the byte represented by the string, or {@code zero} if + * conversion fails + * @since 2.5 + */ + public static byte toByte(final String str) { + return toByte(str, (byte) 0); + } + + /** + * Convert a {@link String} to a {@code byte}, returning a + * default value if the conversion fails. + * + *

If the string is {@code null}, the default value is returned.

+ * + *
+     *   NumberUtils.toByte(null, 1) = 1
+     *   NumberUtils.toByte("", 1)   = 1
+     *   NumberUtils.toByte("1", 0)  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the byte represented by the string, or the default if conversion fails + * @since 2.5 + */ + public static byte toByte(final String str, final byte defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Byte.parseByte(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Convert a {@link BigDecimal} to a {@code double}. + * + *

If the {@link BigDecimal} {@code value} is + * {@code null}, then the specified default value is returned.

+ * + *
+     *   NumberUtils.toDouble(null)                     = 0.0d
+     *   NumberUtils.toDouble(BigDecimal.valueOf(8.5d)) = 8.5d
+     * 
+ * + * @param value the {@link BigDecimal} to convert, may be {@code null}. + * @return the double represented by the {@link BigDecimal} or + * {@code 0.0d} if the {@link BigDecimal} is {@code null}. + * @since 3.8 + */ + public static double toDouble(final BigDecimal value) { + return toDouble(value, 0.0d); + } + + /** + * Convert a {@link BigDecimal} to a {@code double}. + * + *

If the {@link BigDecimal} {@code value} is + * {@code null}, then the specified default value is returned.

+ * + *
+     *   NumberUtils.toDouble(null, 1.1d)                     = 1.1d
+     *   NumberUtils.toDouble(BigDecimal.valueOf(8.5d), 1.1d) = 8.5d
+     * 
+ * + * @param value the {@link BigDecimal} to convert, may be {@code null}. + * @param defaultValue the default value + * @return the double represented by the {@link BigDecimal} or the + * defaultValue if the {@link BigDecimal} is {@code null}. + * @since 3.8 + */ + public static double toDouble(final BigDecimal value, final double defaultValue) { + return value == null ? defaultValue : value.doubleValue(); + } + + /** + * Convert a {@link String} to a {@code double}, returning + * {@code 0.0d} if the conversion fails. + * + *

If the string {@code str} is {@code null}, + * {@code 0.0d} is returned.

+ * + *
+     *   NumberUtils.toDouble(null)   = 0.0d
+     *   NumberUtils.toDouble("")     = 0.0d
+     *   NumberUtils.toDouble("1.5")  = 1.5d
+     * 
+ * + * @param str the string to convert, may be {@code null} + * @return the double represented by the string, or {@code 0.0d} + * if conversion fails + * @since 2.1 + */ + public static double toDouble(final String str) { + return toDouble(str, 0.0d); + } + + /** + * Convert a {@link String} to a {@code double}, returning a + * default value if the conversion fails. + * + *

If the string {@code str} is {@code null}, the default + * value is returned.

+ * + *
+     *   NumberUtils.toDouble(null, 1.1d)   = 1.1d
+     *   NumberUtils.toDouble("", 1.1d)     = 1.1d
+     *   NumberUtils.toDouble("1.5", 0.0d)  = 1.5d
+     * 
+ * + * @param str the string to convert, may be {@code null} + * @param defaultValue the default value + * @return the double represented by the string, or defaultValue + * if conversion fails + * @since 2.1 + */ + public static double toDouble(final String str, final double defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Double.parseDouble(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Convert a {@link String} to a {@code float}, returning + * {@code 0.0f} if the conversion fails. + * + *

If the string {@code str} is {@code null}, + * {@code 0.0f} is returned.

+ * + *
+     *   NumberUtils.toFloat(null)   = 0.0f
+     *   NumberUtils.toFloat("")     = 0.0f
+     *   NumberUtils.toFloat("1.5")  = 1.5f
+     * 
+ * + * @param str the string to convert, may be {@code null} + * @return the float represented by the string, or {@code 0.0f} + * if conversion fails + * @since 2.1 + */ + public static float toFloat(final String str) { + return toFloat(str, 0.0f); + } + + /** + * Convert a {@link String} to a {@code float}, returning a + * default value if the conversion fails. + * + *

If the string {@code str} is {@code null}, the default + * value is returned.

+ * + *
+     *   NumberUtils.toFloat(null, 1.1f)   = 1.0f
+     *   NumberUtils.toFloat("", 1.1f)     = 1.1f
+     *   NumberUtils.toFloat("1.5", 0.0f)  = 1.5f
+     * 
+ * + * @param str the string to convert, may be {@code null} + * @param defaultValue the default value + * @return the float represented by the string, or defaultValue + * if conversion fails + * @since 2.1 + */ + public static float toFloat(final String str, final float defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Float.parseFloat(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Convert a {@link String} to an {@code int}, returning + * {@code zero} if the conversion fails. + * + *

If the string is {@code null}, {@code zero} is returned.

+ * + *
+     *   NumberUtils.toInt(null) = 0
+     *   NumberUtils.toInt("")   = 0
+     *   NumberUtils.toInt("1")  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @return the int represented by the string, or {@code zero} if + * conversion fails + * @since 2.1 + */ + public static int toInt(final String str) { + return toInt(str, 0); + } + + /** + * Convert a {@link String} to an {@code int}, returning a + * default value if the conversion fails. + * + *

If the string is {@code null}, the default value is returned.

+ * + *
+     *   NumberUtils.toInt(null, 1) = 1
+     *   NumberUtils.toInt("", 1)   = 1
+     *   NumberUtils.toInt("1", 0)  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the int represented by the string, or the default if conversion fails + * @since 2.1 + */ + public static int toInt(final String str, final int defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Integer.parseInt(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Convert a {@link String} to a {@code long}, returning + * {@code zero} if the conversion fails. + * + *

If the string is {@code null}, {@code zero} is returned.

+ * + *
+     *   NumberUtils.toLong(null) = 0L
+     *   NumberUtils.toLong("")   = 0L
+     *   NumberUtils.toLong("1")  = 1L
+     * 
+ * + * @param str the string to convert, may be null + * @return the long represented by the string, or {@code 0} if + * conversion fails + * @since 2.1 + */ + public static long toLong(final String str) { + return toLong(str, 0L); + } + + /** + * Convert a {@link String} to a {@code long}, returning a + * default value if the conversion fails. + * + *

If the string is {@code null}, the default value is returned.

+ * + *
+     *   NumberUtils.toLong(null, 1L) = 1L
+     *   NumberUtils.toLong("", 1L)   = 1L
+     *   NumberUtils.toLong("1", 0L)  = 1L
+     * 
+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the long represented by the string, or the default if conversion fails + * @since 2.1 + */ + public static long toLong(final String str, final long defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Long.parseLong(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Convert a {@link BigDecimal} to a {@link BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the + * decimal point.

+ * + * @param value the {@link BigDecimal} to convert, may be null. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final BigDecimal value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@link BigDecimal} to a {@link BigDecimal} whose scale is the + * specified value with a {@link RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@link BigDecimal} to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final BigDecimal value, final int scale, final RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return value.setScale( + scale, + roundingMode == null ? RoundingMode.HALF_EVEN : roundingMode + ); + } + + /** + * Convert a {@link Double} to a {@link BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the + * decimal point.

+ * + * @param value the {@link Double} to convert, may be null. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Double value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@link Double} to a {@link BigDecimal} whose scale is the + * specified value with a {@link RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@link Double} to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Double value, final int scale, final RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + BigDecimal.valueOf(value), + scale, + roundingMode + ); + } + + /** + * Convert a {@link Float} to a {@link BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the + * decimal point.

+ * + * @param value the {@link Float} to convert, may be null. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Float value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@link Float} to a {@link BigDecimal} whose scale is the + * specified value with a {@link RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@link Float} to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final Float value, final int scale, final RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + BigDecimal.valueOf(value), + scale, + roundingMode + ); + } + + /** + * Convert a {@link String} to a {@link BigDecimal} with a scale of + * two that has been rounded using {@code RoundingMode.HALF_EVEN}. If the supplied + * {@code value} is null, then {@code BigDecimal.ZERO} is returned. + * + *

Note, the scale of a {@link BigDecimal} is the number of digits to the right of the + * decimal point.

+ * + * @param value the {@link String} to convert, may be null. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final String value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a {@link String} to a {@link BigDecimal} whose scale is the + * specified value with a {@link RoundingMode} applied. If the input {@code value} + * is {@code null}, we simply return {@code BigDecimal.ZERO}. + * + * @param value the {@link String} to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, {@link BigDecimal}. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(final String value, final int scale, final RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + createBigDecimal(value), + scale, + roundingMode + ); + } + + /** + * Convert a {@link String} to a {@code short}, returning + * {@code zero} if the conversion fails. + * + *

If the string is {@code null}, {@code zero} is returned.

+ * + *
+     *   NumberUtils.toShort(null) = 0
+     *   NumberUtils.toShort("")   = 0
+     *   NumberUtils.toShort("1")  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @return the short represented by the string, or {@code zero} if + * conversion fails + * @since 2.5 + */ + public static short toShort(final String str) { + return toShort(str, (short) 0); + } + + /** + * Convert a {@link String} to an {@code short}, returning a + * default value if the conversion fails. + * + *

If the string is {@code null}, the default value is returned.

+ * + *
+     *   NumberUtils.toShort(null, 1) = 1
+     *   NumberUtils.toShort("", 1)   = 1
+     *   NumberUtils.toShort("1", 0)  = 1
+     * 
+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the short represented by the string, or the default if conversion fails + * @since 2.5 + */ + public static short toShort(final String str, final short defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Short.parseShort(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + /** + * Checks if the specified array is neither null nor empty. + * + * @param array the array to check + * @throws IllegalArgumentException if {@code array} is empty + * @throws NullPointerException if {@code array} is {@code null} + */ + private static void validateArray(final Object array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(Array.getLength(array) != 0, "Array cannot be empty."); + } + private static boolean withDecimalsParsing(final String str, final int beginIdx) { int decimalPoints = 0; for (int i = beginIdx; i < str.length(); i++) { @@ -1784,67 +1839,12 @@ public class NumberUtils { } /** - * Compares two {@code int} values numerically. This is the same functionality as provided in Java 7. + * {@link NumberUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code NumberUtils.toInt("6");}. * - * @param x the first {@code int} to compare - * @param y the second {@code int} to compare - * @return the value {@code 0} if {@code x == y}; - * a value less than {@code 0} if {@code x < y}; and - * a value greater than {@code 0} if {@code x > y} - * @since 3.4 + *

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

*/ - public static int compare(final int x, final int y) { - if (x == y) { - return 0; - } - return x < y ? -1 : 1; - } - - /** - * Compares to {@code long} values numerically. This is the same functionality as provided in Java 7. - * - * @param x the first {@code long} to compare - * @param y the second {@code long} to compare - * @return the value {@code 0} if {@code x == y}; - * a value less than {@code 0} if {@code x < y}; and - * a value greater than {@code 0} if {@code x > y} - * @since 3.4 - */ - public static int compare(final long x, final long y) { - if (x == y) { - return 0; - } - return x < y ? -1 : 1; - } - - /** - * Compares to {@code short} values numerically. This is the same functionality as provided in Java 7. - * - * @param x the first {@code short} to compare - * @param y the second {@code short} to compare - * @return the value {@code 0} if {@code x == y}; - * a value less than {@code 0} if {@code x < y}; and - * a value greater than {@code 0} if {@code x > y} - * @since 3.4 - */ - public static int compare(final short x, final short y) { - if (x == y) { - return 0; - } - return x < y ? -1 : 1; - } - - /** - * Compares two {@code byte} values numerically. This is the same functionality as provided in Java 7. - * - * @param x the first {@code byte} to compare - * @param y the second {@code byte} to compare - * @return the value {@code 0} if {@code x == y}; - * a value less than {@code 0} if {@code x < y}; and - * a value greater than {@code 0} if {@code x > y} - * @since 3.4 - */ - public static int compare(final byte x, final byte y) { - return x - y; + public NumberUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java b/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java index 68511c33b..aa894a65d 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java @@ -67,6 +67,43 @@ public class MutableBoolean implements Mutable, Serializable, Comparabl this.value = value.booleanValue(); } + /** + * Returns the value of this MutableBoolean as a boolean. + * + * @return the boolean value represented by this object. + */ + public boolean booleanValue() { + 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 + * where false is less than true + */ + @Override + public int compareTo(final MutableBoolean other) { + return BooleanUtils.compare(this.value, other.value); + } + + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument is + * not {@code null} and is an {@link MutableBoolean} object that contains the same + * {@code boolean} value as this object. + * + * @param obj the object to compare with, null returns false + * @return {@code true} if the objects are the same; {@code false} otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableBoolean) { + return value == ((MutableBoolean) obj).booleanValue(); + } + return false; + } + /** * Gets the value as a Boolean instance. * @@ -78,12 +115,33 @@ public class MutableBoolean implements Mutable, Serializable, Comparabl } /** - * Sets the value. + * Returns a suitable hash code for this mutable. * - * @param value the value to set + * @return the hash code returned by {@code Boolean.TRUE} or {@code Boolean.FALSE} */ - public void setValue(final boolean value) { - this.value = value; + @Override + public int hashCode() { + return value ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode(); + } + + /** + * Checks if the current value is {@code false}. + * + * @return {@code true} if the current value is {@code false} + * @since 2.5 + */ + public boolean isFalse() { + return !value; + } + + /** + * Checks if the current value is {@code true}. + * + * @return {@code true} if the current value is {@code true} + * @since 2.5 + */ + public boolean isTrue() { + return value; } /** @@ -104,6 +162,15 @@ public class MutableBoolean implements Mutable, Serializable, Comparabl this.value = true; } + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final boolean value) { + this.value = value; + } + /** * Sets the value from any Boolean instance. * @@ -115,35 +182,6 @@ public class MutableBoolean implements Mutable, Serializable, Comparabl this.value = value.booleanValue(); } - /** - * Checks if the current value is {@code true}. - * - * @return {@code true} if the current value is {@code true} - * @since 2.5 - */ - public boolean isTrue() { - return value; - } - - /** - * Checks if the current value is {@code false}. - * - * @return {@code true} if the current value is {@code false} - * @since 2.5 - */ - public boolean isFalse() { - return !value; - } - - /** - * 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. * @@ -154,44 +192,6 @@ public class MutableBoolean implements Mutable, Serializable, Comparabl return Boolean.valueOf(booleanValue()); } - /** - * Compares this object to the specified object. The result is {@code true} if and only if the argument is - * not {@code null} and is an {@link MutableBoolean} object that contains the same - * {@code boolean} value as this object. - * - * @param obj the object to compare with, null returns false - * @return {@code true} if the objects are the same; {@code false} otherwise. - */ - @Override - public boolean equals(final Object obj) { - if (obj instanceof MutableBoolean) { - return value == ((MutableBoolean) obj).booleanValue(); - } - return false; - } - - /** - * Returns a suitable hash code for this mutable. - * - * @return the hash code returned by {@code Boolean.TRUE} or {@code 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 - */ - @Override - public int compareTo(final MutableBoolean other) { - return BooleanUtils.compare(this.value, other.value); - } - /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java b/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java index 94d973805..507adf4ba 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java @@ -75,104 +75,6 @@ public class MutableByte extends Number implements Comparable, Muta this.value = Byte.parseByte(value); } - /** - * Gets the value as a Byte instance. - * - * @return the value as a Byte, never null - */ - @Override - public Byte getValue() { - return Byte.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final Number value) { - this.value = value.byteValue(); - } - - /** - * Increments the value. - * - * @since 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public byte getAndIncrement() { - final byte last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public byte incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public byte getAndDecrement() { - final byte last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public byte decrementAndGet() { - value--; - return value; - } - /** * Adds a value to the value of this instance. * @@ -194,27 +96,6 @@ public class MutableByte extends Number implements Comparable, Muta this.value += operand.byteValue(); } - /** - * Subtracts a value from the value of this instance. - * - * @param operand the value to subtract, not null - * @since 2.2 - */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.byteValue(); - } - /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately after the addition operation. This method is not thread safe. @@ -242,6 +123,85 @@ public class MutableByte extends Number implements Comparable, Muta return value; } + // 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; + } + + /** + * 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 + */ + @Override + public int compareTo(final MutableByte other) { + return NumberUtils.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented + * @since 3.5 + */ + public byte decrementAndGet() { + value--; + 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; + } + + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument is + * not {@code null} and is a {@link MutableByte} object that contains the same {@code byte} value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return {@code true} if the objects are the same; {@code false} otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableByte) { + return value == ((MutableByte) obj).byteValue(); + } + return false; + } + + /** + * 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; + } + /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately prior to the addition operation. This method is not thread safe. @@ -271,14 +231,70 @@ public class MutableByte extends Number implements Comparable, Muta return last; } - // shortValue relies on Number implementation /** - * Returns the value of this MutableByte as a byte. + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. * - * @return the numeric value represented by this object after conversion to type byte. + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public byte getAndDecrement() { + final byte last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public byte getAndIncrement() { + final byte last = value; + value++; + return last; + } + + /** + * Gets the value as a Byte instance. + * + * @return the value as a Byte, never null */ @Override - public byte byteValue() { + public Byte getValue() { + return Byte.valueOf(this.value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + /** + * Increments the value. + * + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public byte incrementAndGet() { + value++; return value; } @@ -303,23 +319,44 @@ public class MutableByte extends Number implements Comparable, Muta } /** - * Returns the value of this MutableByte as a float. + * Sets the value. * - * @return the numeric value represented by this object after conversion to type float. + * @param value the value to set */ - @Override - public float floatValue() { - return value; + public void setValue(final byte value) { + this.value = value; } /** - * Returns the value of this MutableByte as a double. + * Sets the value from any Number instance. * - * @return the numeric value represented by this object after conversion to type double. + * @param value the value to set, not null + * @throws NullPointerException if the object is null */ @Override - public double doubleValue() { - return value; + public void setValue(final Number value) { + this.value = value.byteValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since 2.2 + */ + public void subtract(final 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 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.byteValue(); } /** @@ -331,43 +368,6 @@ public class MutableByte extends Number implements Comparable, Muta return Byte.valueOf(byteValue()); } - /** - * Compares this object to the specified object. The result is {@code true} if and only if the argument is - * not {@code null} and is a {@link MutableByte} object that contains the same {@code byte} value - * as this object. - * - * @param obj the object to compare with, null returns false - * @return {@code true} if the objects are the same; {@code false} otherwise. - */ - @Override - public boolean equals(final 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 - */ - @Override - public int compareTo(final MutableByte other) { - return NumberUtils.compare(this.value, other.value); - } - /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java b/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java index 3f0d0e6bb..d4e352250 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java @@ -73,122 +73,6 @@ public class MutableDouble extends Number implements Comparable, this.value = Double.parseDouble(value); } - /** - * Gets the value as a Double instance. - * - * @return the value as a Double, never null - */ - @Override - public Double getValue() { - return Double.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final 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 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public double getAndIncrement() { - final double last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public double incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public double getAndDecrement() { - final double last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public double decrementAndGet() { - value--; - return value; - } - /** * Adds a value to the value of this instance. * @@ -210,27 +94,6 @@ public class MutableDouble extends Number implements Comparable, this.value += operand.doubleValue(); } - /** - * Subtracts a value from the value of this instance. - * - * @param operand the value to subtract, not null - * @since 2.2 - */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.doubleValue(); - } - /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately after the addition operation. This method is not thread safe. @@ -259,63 +122,35 @@ public class MutableDouble extends Number implements Comparable, } /** - * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately prior to the addition operation. This method is not thread safe. + * Compares this mutable to another in ascending order. * - * @param operand the quantity to add, not null - * @return the value associated with this instance immediately before the operand was added + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableDouble other) { + return Double.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented * @since 3.5 */ - public double getAndAdd(final double operand) { - final double last = value; - this.value += operand; - return last; - } - - /** - * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately prior to the addition operation. This method is not thread safe. - * - * @param operand the quantity to add, not null - * @throws NullPointerException if {@code operand} is null - * @return the value associated with this instance immediately before the operand was added - * @since 3.5 - */ - public double getAndAdd(final Number operand) { - final double last = value; - this.value += operand.doubleValue(); - return last; - } - - // 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; + public double decrementAndGet() { + value--; + return value; } /** @@ -328,15 +163,6 @@ public class MutableDouble extends Number implements Comparable, return value; } - /** - * Gets this mutable as an instance of Double. - * - * @return a Double instance containing the value from this mutable, never null - */ - public Double toDouble() { - return Double.valueOf(doubleValue()); - } - /** * Compares this object against the specified object. The result is {@code true} if and only if the argument * is not {@code null} and is a {@link Double} object that represents a double that has the identical @@ -372,6 +198,81 @@ public class MutableDouble extends Number implements Comparable, && Double.doubleToLongBits(((MutableDouble) obj).value) == Double.doubleToLongBits(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; + } + + /** + * Increments this instance's value by {@code operand}; this method returns the value associated with the instance + * immediately prior to the addition operation. This method is not thread safe. + * + * @param operand the quantity to add, not null + * @return the value associated with this instance immediately before the operand was added + * @since 3.5 + */ + public double getAndAdd(final double operand) { + final double last = value; + this.value += operand; + return last; + } + + /** + * Increments this instance's value by {@code operand}; this method returns the value associated with the instance + * immediately prior to the addition operation. This method is not thread safe. + * + * @param operand the quantity to add, not null + * @throws NullPointerException if {@code operand} is null + * @return the value associated with this instance immediately before the operand was added + * @since 3.5 + */ + public double getAndAdd(final Number operand) { + final double last = value; + this.value += operand.doubleValue(); + return last; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public double getAndDecrement() { + final double last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public double getAndIncrement() { + final double last = value; + value++; + return last; + } + + /** + * Gets the value as a Double instance. + * + * @return the value as a Double, never null + */ + @Override + public Double getValue() { + return Double.valueOf(this.value); + } + /** * Returns a suitable hash code for this mutable. * @@ -384,14 +285,113 @@ public class MutableDouble extends Number implements Comparable, } /** - * Compares this mutable to another in ascending order. + * Increments the value. * - * @param other the other mutable to compare to, not null - * @return negative if this is less, zero if equal, positive if greater + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public double incrementAndGet() { + value++; + return value; + } + + // 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 compareTo(final MutableDouble other) { - return Double.compare(this.value, other.value); + public int intValue() { + return (int) value; + } + + /** + * Checks whether the double value is infinite. + * + * @return true if infinite + */ + public boolean isInfinite() { + return Double.isInfinite(value); + } + + /** + * Checks whether the double value is the special NaN value. + * + * @return true if NaN + */ + public boolean isNaN() { + return Double.isNaN(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; + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final 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 + */ + @Override + public void setValue(final Number value) { + this.value = value.doubleValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since 2.2 + */ + public void subtract(final 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 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.doubleValue(); + } + + /** + * 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()); } /** diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java b/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java index d1aa9f802..4b65aa01d 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java @@ -73,122 +73,6 @@ public class MutableFloat extends Number implements Comparable, Mu this.value = Float.parseFloat(value); } - /** - * Gets the value as a Float instance. - * - * @return the value as a Float, never null - */ - @Override - public Float getValue() { - return Float.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final 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 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public float getAndIncrement() { - final float last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public float incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public float getAndDecrement() { - final float last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public float decrementAndGet() { - value--; - return value; - } - /** * Adds a value to the value of this instance. * @@ -210,27 +94,6 @@ public class MutableFloat extends Number implements Comparable, Mu this.value += operand.floatValue(); } - /** - * Subtracts a value from the value of this instance. - * - * @param operand the value to subtract - * @since 2.2 - */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.floatValue(); - } - /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately after the addition operation. This method is not thread safe. @@ -259,62 +122,34 @@ public class MutableFloat extends Number implements Comparable, Mu } /** - * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately prior to the addition operation. This method is not thread safe. + * Compares this mutable to another in ascending order. * - * @param operand the quantity to add, not null - * @return the value associated with this instance immediately before the operand was added + * @param other the other mutable to compare to, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final MutableFloat other) { + return Float.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented * @since 3.5 */ - public float getAndAdd(final float operand) { - final float last = value; - this.value += operand; - return last; - } - - /** - * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately prior to the addition operation. This method is not thread safe. - * - * @param operand the quantity to add, not null - * @throws NullPointerException if {@code operand} is null - * @return the value associated with this instance immediately before the operand was added - * @since 3.5 - */ - public float getAndAdd(final Number operand) { - final float last = value; - this.value += operand.floatValue(); - return last; - } - - // 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() { + public float decrementAndGet() { + value--; return value; } @@ -328,15 +163,6 @@ public class MutableFloat extends Number implements Comparable, Mu return value; } - /** - * Gets this mutable as an instance of Float. - * - * @return a Float instance containing the value from this mutable, never null - */ - public Float toFloat() { - return Float.valueOf(floatValue()); - } - /** * Compares this object against some other object. The result is {@code true} if and only if the argument is * not {@code null} and is a {@link Float} object that represents a {@code float} that has the @@ -374,6 +200,81 @@ public class MutableFloat extends Number implements Comparable, Mu && Float.floatToIntBits(((MutableFloat) obj).value) == Float.floatToIntBits(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; + } + + /** + * Increments this instance's value by {@code operand}; this method returns the value associated with the instance + * immediately prior to the addition operation. This method is not thread safe. + * + * @param operand the quantity to add, not null + * @return the value associated with this instance immediately before the operand was added + * @since 3.5 + */ + public float getAndAdd(final float operand) { + final float last = value; + this.value += operand; + return last; + } + + /** + * Increments this instance's value by {@code operand}; this method returns the value associated with the instance + * immediately prior to the addition operation. This method is not thread safe. + * + * @param operand the quantity to add, not null + * @throws NullPointerException if {@code operand} is null + * @return the value associated with this instance immediately before the operand was added + * @since 3.5 + */ + public float getAndAdd(final Number operand) { + final float last = value; + this.value += operand.floatValue(); + return last; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public float getAndDecrement() { + final float last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public float getAndIncrement() { + final float last = value; + value++; + return last; + } + + /** + * Gets the value as a Float instance. + * + * @return the value as a Float, never null + */ + @Override + public Float getValue() { + return Float.valueOf(this.value); + } + /** * Returns a suitable hash code for this mutable. * @@ -385,14 +286,113 @@ public class MutableFloat extends Number implements Comparable, Mu } /** - * Compares this mutable to another in ascending order. + * Increments the value. * - * @param other the other mutable to compare to, not null - * @return negative if this is less, zero if equal, positive if greater + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public float incrementAndGet() { + value++; + return value; + } + + // 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 compareTo(final MutableFloat other) { - return Float.compare(this.value, other.value); + public int intValue() { + return (int) value; + } + + /** + * Checks whether the float value is infinite. + * + * @return true if infinite + */ + public boolean isInfinite() { + return Float.isInfinite(value); + } + + /** + * Checks whether the float value is the special NaN value. + * + * @return true if NaN + */ + public boolean isNaN() { + return Float.isNaN(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; + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final 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 + */ + @Override + public void setValue(final Number value) { + this.value = value.floatValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract + * @since 2.2 + */ + public void subtract(final 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 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.floatValue(); + } + + /** + * 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()); } /** diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java b/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java index 99e78b972..03cac1b54 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java @@ -75,104 +75,6 @@ public class MutableInt extends Number implements Comparable, Mutabl this.value = Integer.parseInt(value); } - /** - * Gets the value as a Integer instance. - * - * @return the value as a Integer, never null - */ - @Override - public Integer getValue() { - return Integer.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final Number value) { - this.value = value.intValue(); - } - - /** - * Increments the value. - * - * @since 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public int getAndIncrement() { - final int last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public int incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public int getAndDecrement() { - final int last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public int decrementAndGet() { - value--; - return value; - } - /** * Adds a value to the value of this instance. * @@ -194,27 +96,6 @@ public class MutableInt extends Number implements Comparable, Mutabl this.value += operand.intValue(); } - /** - * Subtracts a value from the value of this instance. - * - * @param operand the value to subtract, not null - * @since 2.2 - */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.intValue(); - } - /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately after the addition operation. This method is not thread safe. @@ -242,6 +123,74 @@ public class MutableInt extends Number implements Comparable, Mutabl 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 + */ + @Override + public int compareTo(final MutableInt other) { + return NumberUtils.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented + * @since 3.5 + */ + public int decrementAndGet() { + value--; + 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; + } + + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument is + * not {@code null} and is a {@link MutableInt} object that contains the same {@code int} value + * as this object. + * + * @param obj the object to compare with, null returns false + * @return {@code true} if the objects are the same; {@code false} otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableInt) { + return value == ((MutableInt) obj).intValue(); + } + return false; + } + + /** + * 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; + } + /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately prior to the addition operation. This method is not thread safe. @@ -271,6 +220,73 @@ public class MutableInt extends Number implements Comparable, Mutabl return last; } + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public int getAndDecrement() { + final int last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public int getAndIncrement() { + final int last = value; + value++; + return last; + } + + /** + * Gets the value as a Integer instance. + * + * @return the value as a Integer, never null + */ + @Override + public Integer getValue() { + return Integer.valueOf(this.value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + /** + * Increments the value. + * + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public int incrementAndGet() { + value++; + return value; + } + // shortValue and byteValue rely on Number implementation /** * Returns the value of this MutableInt as an int. @@ -293,23 +309,44 @@ public class MutableInt extends Number implements Comparable, Mutabl } /** - * Returns the value of this MutableInt as a float. + * Sets the value. * - * @return the numeric value represented by this object after conversion to type float. + * @param value the value to set */ - @Override - public float floatValue() { - return value; + public void setValue(final int value) { + this.value = value; } /** - * Returns the value of this MutableInt as a double. + * Sets the value from any Number instance. * - * @return the numeric value represented by this object after conversion to type double. + * @param value the value to set, not null + * @throws NullPointerException if the object is null */ @Override - public double doubleValue() { - return value; + public void setValue(final Number value) { + this.value = value.intValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since 2.2 + */ + public void subtract(final 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 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.intValue(); } /** @@ -321,43 +358,6 @@ public class MutableInt extends Number implements Comparable, Mutabl return Integer.valueOf(intValue()); } - /** - * Compares this object to the specified object. The result is {@code true} if and only if the argument is - * not {@code null} and is a {@link MutableInt} object that contains the same {@code int} value - * as this object. - * - * @param obj the object to compare with, null returns false - * @return {@code true} if the objects are the same; {@code false} otherwise. - */ - @Override - public boolean equals(final 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 - */ - @Override - public int compareTo(final MutableInt other) { - return NumberUtils.compare(this.value, other.value); - } - /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java b/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java index 651c6994f..cec05aecd 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java @@ -75,104 +75,6 @@ public class MutableLong extends Number implements Comparable, Muta this.value = Long.parseLong(value); } - /** - * Gets the value as a Long instance. - * - * @return the value as a Long, never null - */ - @Override - public Long getValue() { - return Long.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final Number value) { - this.value = value.longValue(); - } - - /** - * Increments the value. - * - * @since 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public long getAndIncrement() { - final long last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public long incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public long getAndDecrement() { - final long last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public long decrementAndGet() { - value--; - return value; - } - /** * Adds a value to the value of this instance. * @@ -194,27 +96,6 @@ public class MutableLong extends Number implements Comparable, Muta this.value += operand.longValue(); } - /** - * Subtracts a value from the value of this instance. - * - * @param operand the value to subtract, not null - * @since 2.2 - */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.longValue(); - } - /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately after the addition operation. This method is not thread safe. @@ -242,6 +123,74 @@ public class MutableLong extends Number implements Comparable, Muta 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 + */ + @Override + public int compareTo(final MutableLong other) { + return NumberUtils.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented + * @since 3.5 + */ + public long decrementAndGet() { + value--; + 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; + } + + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument + * is not {@code null} and is a {@link MutableLong} object that contains the same {@code long} + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return {@code true} if the objects are the same; {@code false} otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableLong) { + return value == ((MutableLong) obj).longValue(); + } + return false; + } + + /** + * 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; + } + /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance * immediately prior to the addition operation. This method is not thread safe. @@ -271,6 +220,73 @@ public class MutableLong extends Number implements Comparable, Muta return last; } + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public long getAndDecrement() { + final long last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public long getAndIncrement() { + final long last = value; + value++; + return last; + } + + /** + * Gets the value as a Long instance. + * + * @return the value as a Long, never null + */ + @Override + public Long getValue() { + return Long.valueOf(this.value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return (int) (value ^ (value >>> 32)); + } + + /** + * Increments the value. + * + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public long incrementAndGet() { + value++; + return value; + } + // shortValue and byteValue rely on Number implementation /** * Returns the value of this MutableLong as an int. @@ -293,23 +309,44 @@ public class MutableLong extends Number implements Comparable, Muta } /** - * Returns the value of this MutableLong as a float. + * Sets the value. * - * @return the numeric value represented by this object after conversion to type float. + * @param value the value to set */ - @Override - public float floatValue() { - return value; + public void setValue(final long value) { + this.value = value; } /** - * Returns the value of this MutableLong as a double. + * Sets the value from any Number instance. * - * @return the numeric value represented by this object after conversion to type double. + * @param value the value to set, not null + * @throws NullPointerException if the object is null */ @Override - public double doubleValue() { - return value; + public void setValue(final Number value) { + this.value = value.longValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since 2.2 + */ + public void subtract(final 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 2.2 + */ + public void subtract(final Number operand) { + this.value -= operand.longValue(); } /** @@ -321,43 +358,6 @@ public class MutableLong extends Number implements Comparable, Muta return Long.valueOf(longValue()); } - /** - * Compares this object to the specified object. The result is {@code true} if and only if the argument - * is not {@code null} and is a {@link MutableLong} object that contains the same {@code long} - * value as this object. - * - * @param obj the object to compare with, null returns false - * @return {@code true} if the objects are the same; {@code false} otherwise. - */ - @Override - public boolean equals(final 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 - */ - @Override - public int compareTo(final MutableLong other) { - return NumberUtils.compare(this.value, other.value); - } - /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java b/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java index 259d007b0..9be6f3938 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java @@ -53,26 +53,6 @@ public class MutableObject implements Mutable, Serializable { this.value = value; } - /** - * Gets the value. - * - * @return the value, may be null - */ - @Override - public T getValue() { - return this.value; - } - - /** - * Sets the value. - * - * @param value the value to set - */ - @Override - public void setValue(final T value) { - this.value = value; - } - /** * Compares this object against the specified object. The result is {@code true} if and only if the argument * is not {@code null} and is a {@link MutableObject} object that contains the same {@link T} @@ -98,6 +78,16 @@ public class MutableObject implements Mutable, Serializable { return false; } + /** + * Gets the value. + * + * @return the value, may be null + */ + @Override + public T getValue() { + return this.value; + } + /** * Returns the value's hash code or {@code 0} if the value is {@code null}. * @@ -108,6 +98,16 @@ public class MutableObject implements Mutable, Serializable { return Objects.hashCode(value); } + /** + * Sets the value. + * + * @param value the value to set + */ + @Override + public void setValue(final T value) { + this.value = value; + } + /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java b/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java index 4378ad190..bee881589 100644 --- a/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java +++ b/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java @@ -45,15 +45,6 @@ public class MutableShort extends Number implements Comparable, Mu public MutableShort() { } - /** - * Constructs a new MutableShort with the specified value. - * - * @param value the initial value to store - */ - public MutableShort(final short value) { - this.value = value; - } - /** * Constructs a new MutableShort with the specified value. * @@ -64,6 +55,15 @@ public class MutableShort extends Number implements Comparable, Mu this.value = value.shortValue(); } + /** + * Constructs a new MutableShort with the specified value. + * + * @param value the initial value to store + */ + public MutableShort(final short value) { + this.value = value; + } + /** * Constructs a new MutableShort parsing the given string. * @@ -75,114 +75,6 @@ public class MutableShort extends Number implements Comparable, Mu this.value = Short.parseShort(value); } - /** - * Gets the value as a Short instance. - * - * @return the value as a Short, never null - */ - @Override - public Short getValue() { - return Short.valueOf(this.value); - } - - /** - * Sets the value. - * - * @param value the value to set - */ - public void setValue(final 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 - */ - @Override - public void setValue(final Number value) { - this.value = value.shortValue(); - } - - /** - * Increments the value. - * - * @since 2.2 - */ - public void increment() { - value++; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the increment operation. This method is not thread safe. - * - * @return the value associated with the instance before it was incremented - * @since 3.5 - */ - public short getAndIncrement() { - final short last = value; - value++; - return last; - } - - /** - * Increments this instance's value by 1; this method returns the value associated with the instance - * immediately after the increment operation. This method is not thread safe. - * - * @return the value associated with the instance after it is incremented - * @since 3.5 - */ - public short incrementAndGet() { - value++; - return value; - } - - /** - * Decrements the value. - * - * @since 2.2 - */ - public void decrement() { - value--; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately prior to the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance before it was decremented - * @since 3.5 - */ - public short getAndDecrement() { - final short last = value; - value--; - return last; - } - - /** - * Decrements this instance's value by 1; this method returns the value associated with the instance - * immediately after the decrement operation. This method is not thread safe. - * - * @return the value associated with the instance after it is decremented - * @since 3.5 - */ - public short decrementAndGet() { - value--; - return value; - } - - /** - * Adds a value to the value of this instance. - * - * @param operand the value to add, not null - * @since 2.2 - */ - public void add(final short operand) { - this.value += operand; - } - /** * Adds a value to the value of this instance. * @@ -195,37 +87,13 @@ public class MutableShort extends Number implements Comparable, Mu } /** - * Subtracts a value from the value of this instance. + * Adds a value to the value of this instance. * - * @param operand the value to subtract, not null + * @param operand the value to add, not null * @since 2.2 */ - public void subtract(final 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 2.2 - */ - public void subtract(final Number operand) { - this.value -= operand.shortValue(); - } - - /** - * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately after the addition operation. This method is not thread safe. - * - * @param operand the quantity to add, not null - * @return the value associated with this instance after adding the operand - * @since 3.5 - */ - public short addAndGet(final short operand) { + public void add(final short operand) { this.value += operand; - return value; } /** @@ -244,16 +112,83 @@ public class MutableShort extends Number implements Comparable, Mu /** * Increments this instance's value by {@code operand}; this method returns the value associated with the instance - * immediately prior to the addition operation. This method is not thread safe. + * immediately after the addition operation. This method is not thread safe. * * @param operand the quantity to add, not null - * @return the value associated with this instance immediately before the operand was added + * @return the value associated with this instance after adding the operand * @since 3.5 */ - public short getAndAdd(final short operand) { - final short last = value; + public short addAndGet(final short operand) { this.value += operand; - return last; + 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 + */ + @Override + public int compareTo(final MutableShort other) { + return NumberUtils.compare(this.value, other.value); + } + + /** + * Decrements the value. + * + * @since 2.2 + */ + public void decrement() { + value--; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately after the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance after it is decremented + * @since 3.5 + */ + public short decrementAndGet() { + value--; + 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; + } + + /** + * Compares this object to the specified object. The result is {@code true} if and only if the argument + * is not {@code null} and is a {@link MutableShort} object that contains the same {@code short} + * value as this object. + * + * @param obj the object to compare with, null returns false + * @return {@code true} if the objects are the same; {@code false} otherwise. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof MutableShort) { + return value == ((MutableShort) obj).shortValue(); + } + return false; + } + + /** + * 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; } /** @@ -271,14 +206,84 @@ public class MutableShort extends Number implements Comparable, Mu return last; } - // byteValue relies on Number implementation /** - * Returns the value of this MutableShort as a short. + * Increments this instance's value by {@code operand}; this method returns the value associated with the instance + * immediately prior to the addition operation. This method is not thread safe. * - * @return the numeric value represented by this object after conversion to type short. + * @param operand the quantity to add, not null + * @return the value associated with this instance immediately before the operand was added + * @since 3.5 + */ + public short getAndAdd(final short operand) { + final short last = value; + this.value += operand; + return last; + } + + /** + * Decrements this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the decrement operation. This method is not thread safe. + * + * @return the value associated with the instance before it was decremented + * @since 3.5 + */ + public short getAndDecrement() { + final short last = value; + value--; + return last; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately prior to the increment operation. This method is not thread safe. + * + * @return the value associated with the instance before it was incremented + * @since 3.5 + */ + public short getAndIncrement() { + final short last = value; + value++; + return last; + } + + /** + * Gets the value as a Short instance. + * + * @return the value as a Short, never null */ @Override - public short shortValue() { + public Short getValue() { + return Short.valueOf(this.value); + } + + /** + * Returns a suitable hash code for this mutable. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return value; + } + + /** + * Increments the value. + * + * @since 2.2 + */ + public void increment() { + value++; + } + + /** + * Increments this instance's value by 1; this method returns the value associated with the instance + * immediately after the increment operation. This method is not thread safe. + * + * @return the value associated with the instance after it is incremented + * @since 3.5 + */ + public short incrementAndGet() { + value++; return value; } @@ -303,23 +308,55 @@ public class MutableShort extends Number implements Comparable, Mu } /** - * Returns the value of this MutableShort as a float. + * Sets the value from any Number instance. * - * @return the numeric value represented by this object after conversion to type float. + * @param value the value to set, not null + * @throws NullPointerException if the object is null */ @Override - public float floatValue() { + public void setValue(final Number value) { + this.value = value.shortValue(); + } + + /** + * Sets the value. + * + * @param value the value to set + */ + public void setValue(final short value) { + this.value = value; + } + + // 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 a double. + * Subtracts a value from the value of this instance. * - * @return the numeric value represented by this object after conversion to type double. + * @param operand the value to subtract, not null + * @throws NullPointerException if the object is null + * @since 2.2 */ - @Override - public double doubleValue() { - return value; + public void subtract(final Number operand) { + this.value -= operand.shortValue(); + } + + /** + * Subtracts a value from the value of this instance. + * + * @param operand the value to subtract, not null + * @since 2.2 + */ + public void subtract(final short operand) { + this.value -= operand; } /** @@ -331,43 +368,6 @@ public class MutableShort extends Number implements Comparable, Mu return Short.valueOf(shortValue()); } - /** - * Compares this object to the specified object. The result is {@code true} if and only if the argument - * is not {@code null} and is a {@link MutableShort} object that contains the same {@code short} - * value as this object. - * - * @param obj the object to compare with, null returns false - * @return {@code true} if the objects are the same; {@code false} otherwise. - */ - @Override - public boolean equals(final 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 - */ - @Override - public int compareTo(final MutableShort other) { - return NumberUtils.compare(this.value, other.value); - } - /** * Returns the String value of this mutable. * diff --git a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java index fda2fa7f7..cae26a38a 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java @@ -46,14 +46,99 @@ import org.apache.commons.lang3.ClassUtils; public class ConstructorUtils { /** - * ConstructorUtils instances should NOT be constructed in standard - * programming. Instead, the class should be used as - * {@code ConstructorUtils.invokeConstructor(cls, args)}. + * Finds a constructor given a class and signature, checking accessibility. * - *

This constructor is {@code public} to permit tools that require a JavaBean - * instance to operate.

+ *

This finds the constructor and ensures that it is accessible. + * The constructor signature must match the parameter types exactly.

+ * + * @param the constructor type + * @param cls the class to find a constructor for, not {@code null} + * @param parameterTypes the array of parameter types, {@code null} treated as empty + * @return the constructor, {@code null} if no matching accessible constructor found + * @see Class#getConstructor + * @see #getAccessibleConstructor(java.lang.reflect.Constructor) + * @throws NullPointerException if {@code cls} is {@code null} */ - public ConstructorUtils() { + public static Constructor getAccessibleConstructor(final Class cls, + final Class... parameterTypes) { + Objects.requireNonNull(cls, "cls"); + try { + return getAccessibleConstructor(cls.getConstructor(parameterTypes)); + } catch (final NoSuchMethodException e) { + return null; + } + } + + /** + * Checks if the specified constructor is accessible. + * + *

This simply ensures that the constructor is accessible.

+ * + * @param the constructor type + * @param ctor the prototype constructor object, not {@code null} + * @return the constructor, {@code null} if no matching accessible constructor found + * @see SecurityManager + * @throws NullPointerException if {@code ctor} is {@code null} + */ + public static Constructor getAccessibleConstructor(final Constructor ctor) { + Objects.requireNonNull(ctor, "ctor"); + return MemberUtils.isAccessible(ctor) + && isAccessible(ctor.getDeclaringClass()) ? ctor : null; + } + + /** + * Finds an accessible constructor with compatible parameters. + * + *

This checks all the constructor and finds one with compatible parameters + * This requires that every parameter is assignable from the given parameter types. + * This is a more flexible search than the normal exact matching algorithm.

+ * + *

First it checks if there is a constructor matching the exact signature. + * If not then all the constructors of the class are checked to see if their + * signatures are assignment-compatible with the parameter types. + * The first assignment-compatible matching constructor is returned.

+ * + * @param the constructor type + * @param cls the class to find a constructor for, not {@code null} + * @param parameterTypes find method with compatible parameters + * @return the constructor, null if no matching accessible constructor found + * @throws NullPointerException if {@code cls} is {@code null} + */ + public static Constructor getMatchingAccessibleConstructor(final Class cls, + final Class... parameterTypes) { + Objects.requireNonNull(cls, "cls"); + // see if we can find the constructor directly + // most of the time this works and it's much faster + try { + return MemberUtils.setAccessibleWorkaround(cls.getConstructor(parameterTypes)); + } catch (final NoSuchMethodException ignored) { + // ignore + } + Constructor result = null; + /* + * (1) Class.getConstructors() is documented to return Constructor so as + * long as the array is not subsequently modified, everything's fine. + */ + final Constructor[] ctors = cls.getConstructors(); + + // return best match: + for (Constructor ctor : ctors) { + // compare parameters + if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) { + // get accessible version of constructor + ctor = getAccessibleConstructor(ctor); + if (ctor != null) { + MemberUtils.setAccessibleWorkaround(ctor); + if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) { + // temporary variable for annotation, see comment above (1) + @SuppressWarnings("unchecked") + final Constructor constructor = (Constructor) ctor; + result = constructor; + } + } + } + } + return result; } /** @@ -178,102 +263,6 @@ public class ConstructorUtils { return ctor.newInstance(args); } - /** - * Finds a constructor given a class and signature, checking accessibility. - * - *

This finds the constructor and ensures that it is accessible. - * The constructor signature must match the parameter types exactly.

- * - * @param the constructor type - * @param cls the class to find a constructor for, not {@code null} - * @param parameterTypes the array of parameter types, {@code null} treated as empty - * @return the constructor, {@code null} if no matching accessible constructor found - * @see Class#getConstructor - * @see #getAccessibleConstructor(java.lang.reflect.Constructor) - * @throws NullPointerException if {@code cls} is {@code null} - */ - public static Constructor getAccessibleConstructor(final Class cls, - final Class... parameterTypes) { - Objects.requireNonNull(cls, "cls"); - try { - return getAccessibleConstructor(cls.getConstructor(parameterTypes)); - } catch (final NoSuchMethodException e) { - return null; - } - } - - /** - * Checks if the specified constructor is accessible. - * - *

This simply ensures that the constructor is accessible.

- * - * @param the constructor type - * @param ctor the prototype constructor object, not {@code null} - * @return the constructor, {@code null} if no matching accessible constructor found - * @see SecurityManager - * @throws NullPointerException if {@code ctor} is {@code null} - */ - public static Constructor getAccessibleConstructor(final Constructor ctor) { - Objects.requireNonNull(ctor, "ctor"); - return MemberUtils.isAccessible(ctor) - && isAccessible(ctor.getDeclaringClass()) ? ctor : null; - } - - /** - * Finds an accessible constructor with compatible parameters. - * - *

This checks all the constructor and finds one with compatible parameters - * This requires that every parameter is assignable from the given parameter types. - * This is a more flexible search than the normal exact matching algorithm.

- * - *

First it checks if there is a constructor matching the exact signature. - * If not then all the constructors of the class are checked to see if their - * signatures are assignment-compatible with the parameter types. - * The first assignment-compatible matching constructor is returned.

- * - * @param the constructor type - * @param cls the class to find a constructor for, not {@code null} - * @param parameterTypes find method with compatible parameters - * @return the constructor, null if no matching accessible constructor found - * @throws NullPointerException if {@code cls} is {@code null} - */ - public static Constructor getMatchingAccessibleConstructor(final Class cls, - final Class... parameterTypes) { - Objects.requireNonNull(cls, "cls"); - // see if we can find the constructor directly - // most of the time this works and it's much faster - try { - return MemberUtils.setAccessibleWorkaround(cls.getConstructor(parameterTypes)); - } catch (final NoSuchMethodException ignored) { - // ignore - } - Constructor result = null; - /* - * (1) Class.getConstructors() is documented to return Constructor so as - * long as the array is not subsequently modified, everything's fine. - */ - final Constructor[] ctors = cls.getConstructors(); - - // return best match: - for (Constructor ctor : ctors) { - // compare parameters - if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) { - // get accessible version of constructor - ctor = getAccessibleConstructor(ctor); - if (ctor != null) { - MemberUtils.setAccessibleWorkaround(ctor); - if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) { - // temporary variable for annotation, see comment above (1) - @SuppressWarnings("unchecked") - final Constructor constructor = (Constructor) ctor; - result = constructor; - } - } - } - } - return result; - } - /** * Tests whether the specified class is generally accessible, i.e. is * declared in an entirely {@code public} manner. @@ -292,4 +281,15 @@ public class ConstructorUtils { return true; } + /** + * ConstructorUtils instances should NOT be constructed in standard + * programming. Instead, the class should be used as + * {@code ConstructorUtils.invokeConstructor(cls, args)}. + * + *

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

+ */ + public ConstructorUtils() { + } + } diff --git a/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java b/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java index d863c250c..b81a7e679 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java @@ -44,12 +44,93 @@ import org.apache.commons.lang3.Validate; public class FieldUtils { /** - * {@link FieldUtils} instances should NOT be constructed in standard programming. - *

- * This constructor is {@code public} to permit tools that require a JavaBean instance to operate. - *

+ * Gets all fields of the given class and its parents (if any). + * + * @param cls + * the {@link Class} to query + * @return an array of Fields (possibly empty). + * @throws NullPointerException + * if the class is {@code null} + * @since 3.2 */ - public FieldUtils() { + public static Field[] getAllFields(final Class cls) { + return getAllFieldsList(cls).toArray(ArrayUtils.EMPTY_FIELD_ARRAY); + } + + /** + * Gets all fields of the given class and its parents (if any). + * + * @param cls + * the {@link Class} to query + * @return a list of Fields (possibly empty). + * @throws NullPointerException + * if the class is {@code null} + * @since 3.2 + */ + public static List getAllFieldsList(final Class cls) { + Objects.requireNonNull(cls, "cls"); + final List allFields = new ArrayList<>(); + Class currentClass = cls; + while (currentClass != null) { + final Field[] declaredFields = currentClass.getDeclaredFields(); + Collections.addAll(allFields, declaredFields); + currentClass = currentClass.getSuperclass(); + } + return allFields; + } + + /** + * Gets an accessible {@link Field} by name respecting scope. Only the specified class will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the Field object + * @throws NullPointerException + * if the class is {@code null} + * @throws IllegalArgumentException + * if the field name is {@code null}, blank, or empty + */ + public static Field getDeclaredField(final Class cls, final String fieldName) { + return getDeclaredField(cls, fieldName, false); + } + + /** + * Gets an accessible {@link Field} by name, breaking scope if requested. Only the specified class will be + * considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws NullPointerException + * if the class is {@code null} + * @throws IllegalArgumentException + * if the field name is {@code null}, blank, or empty + */ + public static Field getDeclaredField(final Class cls, final String fieldName, final boolean forceAccess) { + Objects.requireNonNull(cls, "cls"); + Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); + try { + // only consider the specified class by using getDeclaredField() + final Field field = cls.getDeclaredField(fieldName); + if (!MemberUtils.isAccessible(field)) { + if (!forceAccess) { + return null; + } + field.setAccessible(true); + } + return field; + } catch (final NoSuchFieldException ignored) { + // ignore + } + return null; } /** @@ -138,93 +219,19 @@ public class FieldUtils { } /** - * Gets an accessible {@link Field} by name respecting scope. Only the specified class will be considered. - * - * @param cls - * the {@link Class} to reflect, must not be {@code null} - * @param fieldName - * the field name to obtain - * @return the Field object - * @throws NullPointerException - * if the class is {@code null} - * @throws IllegalArgumentException - * if the field name is {@code null}, blank, or empty - */ - public static Field getDeclaredField(final Class cls, final String fieldName) { - return getDeclaredField(cls, fieldName, false); - } - - /** - * Gets an accessible {@link Field} by name, breaking scope if requested. Only the specified class will be - * considered. - * - * @param cls - * the {@link Class} to reflect, must not be {@code null} - * @param fieldName - * the field name to obtain - * @param forceAccess - * whether to break scope restrictions using the - * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only - * match {@code public} fields. - * @return the Field object - * @throws NullPointerException - * if the class is {@code null} - * @throws IllegalArgumentException - * if the field name is {@code null}, blank, or empty - */ - public static Field getDeclaredField(final Class cls, final String fieldName, final boolean forceAccess) { - Objects.requireNonNull(cls, "cls"); - Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); - try { - // only consider the specified class by using getDeclaredField() - final Field field = cls.getDeclaredField(fieldName); - if (!MemberUtils.isAccessible(field)) { - if (!forceAccess) { - return null; - } - field.setAccessible(true); - } - return field; - } catch (final NoSuchFieldException ignored) { - // ignore - } - return null; - } - - /** - * Gets all fields of the given class and its parents (if any). - * - * @param cls - * the {@link Class} to query - * @return an array of Fields (possibly empty). - * @throws NullPointerException - * if the class is {@code null} - * @since 3.2 - */ - public static Field[] getAllFields(final Class cls) { - return getAllFieldsList(cls).toArray(ArrayUtils.EMPTY_FIELD_ARRAY); - } - - /** - * Gets all fields of the given class and its parents (if any). - * + * Gets all fields of the given class and its parents (if any) that are annotated with the given annotation. * @param cls * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a field to be matched * @return a list of Fields (possibly empty). * @throws NullPointerException - * if the class is {@code null} - * @since 3.2 + * if the class or annotation are {@code null} + * @since 3.4 */ - public static List getAllFieldsList(final Class cls) { - Objects.requireNonNull(cls, "cls"); - final List allFields = new ArrayList<>(); - Class currentClass = cls; - while (currentClass != null) { - final Field[] declaredFields = currentClass.getDeclaredFields(); - Collections.addAll(allFields, declaredFields); - currentClass = currentClass.getSuperclass(); - } - return allFields; + public static List getFieldsListWithAnnotation(final Class cls, final Class annotationCls) { + Objects.requireNonNull(annotationCls, "annotationCls"); + return getAllFieldsList(cls).stream().filter(field -> field.getAnnotation(annotationCls) != null).collect(Collectors.toList()); } /** @@ -243,103 +250,50 @@ public class FieldUtils { } /** - * Gets all fields of the given class and its parents (if any) that are annotated with the given annotation. - * @param cls - * the {@link Class} to query - * @param annotationCls - * the {@link Annotation} that must be present on a field to be matched - * @return a list of Fields (possibly empty). - * @throws NullPointerException - * if the class or annotation are {@code null} - * @since 3.4 - */ - public static List getFieldsListWithAnnotation(final Class cls, final Class annotationCls) { - Objects.requireNonNull(annotationCls, "annotationCls"); - return getAllFieldsList(cls).stream().filter(field -> field.getAnnotation(annotationCls) != null).collect(Collectors.toList()); - } - - /** - * Reads an accessible {@code static} {@link Field}. + * Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered. * - * @param field - * to read - * @return the field value - * @throws NullPointerException - * if the field is {@code null} - * @throws IllegalArgumentException - * if the field is not {@code static} - * @throws IllegalAccessException - * if the field is not accessible - */ - public static Object readStaticField(final Field field) throws IllegalAccessException { - return readStaticField(field, false); - } - - /** - * Reads a static {@link Field}. - * - * @param field - * to read - * @param forceAccess - * whether to break scope restrictions using the - * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. - * @return the field value - * @throws NullPointerException - * if the field is {@code null} - * @throws IllegalArgumentException - * if the field is not {@code static} - * @throws IllegalAccessException - * if the field is not made accessible - */ - public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException { - Objects.requireNonNull(field, "field"); - Validate.isTrue(MemberUtils.isStatic(field), "The field '%s' is not static", field.getName()); - return readField(field, (Object) null, forceAccess); - } - - /** - * Reads the named {@code public static} {@link Field}. Superclasses will be considered. - * - * @param cls - * the {@link Class} to reflect, must not be {@code null} + * @param target + * the object to reflect, must not be {@code null} * @param fieldName * the field name to obtain * @return the value of the field * @throws NullPointerException - * if the class is {@code null}, or the field could not be found + * if {@code target} is @{code null} * @throws IllegalArgumentException - * if the field name is {@code null}, blank or empty, or is not {@code static} + * if {@code fieldName} is {@code null}, blank or empty, or could not be found * @throws IllegalAccessException - * if the field is not accessible + * if the named field is not {@code public} */ - public static Object readStaticField(final Class cls, final String fieldName) throws IllegalAccessException { - return readStaticField(cls, fieldName, false); + public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException { + return readDeclaredField(target, fieldName, false); } /** - * Reads the named {@code static} {@link Field}. Superclasses will be considered. + * Gets a {@link Field} value by name. Only the class of the specified object will be considered. * - * @param cls - * the {@link Class} to reflect, must not be {@code null} + * @param target + * the object to reflect, must not be {@code null} * @param fieldName * the field name to obtain * @param forceAccess * whether to break scope restrictions using the * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only - * match {@code public} fields. + * match public fields. * @return the Field object * @throws NullPointerException - * if the class is {@code null}, or the field could not be found + * if {@code target} is @{code null} * @throws IllegalArgumentException - * if the field name is {@code null}, blank or empty, or is not {@code static} + * if {@code fieldName} is {@code null}, blank or empty, or could not be found * @throws IllegalAccessException * if the field is not made accessible */ - public static Object readStaticField(final Class cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException { - final Field field = getField(cls, fieldName, forceAccess); - Validate.notNull(field, "Cannot locate field '%s' on %s", fieldName, cls); + public static Object readDeclaredField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + Objects.requireNonNull(target, "target"); + final Class cls = target.getClass(); + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName); // already forced access above, don't repeat it here: - return readStaticField(field, false); + return readField(field, target, false); } /** @@ -479,142 +433,197 @@ public class FieldUtils { } /** - * Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered. + * Reads the named {@code public static} {@link Field}. Superclasses will be considered. * - * @param target - * the object to reflect, must not be {@code null} + * @param cls + * the {@link Class} to reflect, must not be {@code null} * @param fieldName * the field name to obtain * @return the value of the field * @throws NullPointerException - * if {@code target} is @{code null} + * if the class is {@code null}, or the field could not be found * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, or could not be found + * if the field name is {@code null}, blank or empty, or is not {@code static} * @throws IllegalAccessException - * if the named field is not {@code public} + * if the field is not accessible */ - public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException { - return readDeclaredField(target, fieldName, false); + public static Object readStaticField(final Class cls, final String fieldName) throws IllegalAccessException { + return readStaticField(cls, fieldName, false); } /** - * Gets a {@link Field} value by name. Only the class of the specified object will be considered. + * Reads the named {@code static} {@link Field}. Superclasses will be considered. * - * @param target - * the object to reflect, must not be {@code null} + * @param cls + * the {@link Class} to reflect, must not be {@code null} * @param fieldName * the field name to obtain * @param forceAccess * whether to break scope restrictions using the * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only - * match public fields. + * match {@code public} fields. * @return the Field object * @throws NullPointerException - * if {@code target} is @{code null} + * if the class is {@code null}, or the field could not be found * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, or could not be found + * if the field name is {@code null}, blank or empty, or is not {@code static} * @throws IllegalAccessException * if the field is not made accessible */ - public static Object readDeclaredField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException { - Objects.requireNonNull(target, "target"); - final Class cls = target.getClass(); - final Field field = getDeclaredField(cls, fieldName, forceAccess); - Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName); + public static Object readStaticField(final Class cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException { + final Field field = getField(cls, fieldName, forceAccess); + Validate.notNull(field, "Cannot locate field '%s' on %s", fieldName, cls); // already forced access above, don't repeat it here: - return readField(field, target, false); + return readStaticField(field, false); } /** - * Writes a {@code public static} {@link Field}. + * Reads an accessible {@code static} {@link Field}. * * @param field - * to write - * @param value - * to set + * to read + * @return the field value * @throws NullPointerException - * if the field is {@code null} + * if the field is {@code null} * @throws IllegalArgumentException - * if the field is not {@code static}, or {@code value} is not assignable + * if the field is not {@code static} * @throws IllegalAccessException - * if the field is not {@code public} or is {@code final} + * if the field is not accessible */ - public static void writeStaticField(final Field field, final Object value) throws IllegalAccessException { - writeStaticField(field, value, false); + public static Object readStaticField(final Field field) throws IllegalAccessException { + return readStaticField(field, false); } /** - * Writes a static {@link Field}. + * Reads a static {@link Field}. * * @param field - * to write - * @param value - * to set + * to read + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. + * @return the field value + * @throws NullPointerException + * if the field is {@code null} + * @throws IllegalArgumentException + * if the field is not {@code static} + * @throws IllegalAccessException + * if the field is not made accessible + */ + public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException { + Objects.requireNonNull(field, "field"); + Validate.isTrue(MemberUtils.isStatic(field), "The field '%s' is not static", field.getName()); + return readField(field, (Object) null, forceAccess); + } + + /** + * Removes the final modifier from a {@link Field}. + * + * @param field + * to remove the final modifier + * @throws NullPointerException + * if the field is {@code null} + * @since 3.2 + */ + public static void removeFinalModifier(final Field field) { + removeFinalModifier(field, true); + } + + /** + * Removes the final modifier from a {@link Field}. + * + * @param field + * to remove the final modifier * @param forceAccess * whether to break scope restrictions using the * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only * match {@code public} fields. * @throws NullPointerException - * if the field is {@code null} - * @throws IllegalArgumentException - * if the field is not {@code static}, or {@code value} is not assignable - * @throws IllegalAccessException - * if the field is not made accessible or is {@code final} + * if the field is {@code null} + * @deprecated As of Java 12, we can no longer drop the {@code final} modifier, thus + * rendering this method obsolete. The JDK discussion about this change can be found + * here: https://mail.openjdk.java.net/pipermail/core-libs-dev/2018-November/056486.html + * @since 3.3 */ - public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException { + @Deprecated + public static void removeFinalModifier(final Field field, final boolean forceAccess) { Objects.requireNonNull(field, "field"); - Validate.isTrue(MemberUtils.isStatic(field), "The field %s.%s is not static", field.getDeclaringClass().getName(), - field.getName()); - writeField(field, (Object) null, value, forceAccess); + + try { + if (Modifier.isFinal(field.getModifiers())) { + // Do all JREs implement Field with a private ivar called "modifiers"? + final Field modifiersField = Field.class.getDeclaredField("modifiers"); + final boolean doForceAccess = forceAccess && !modifiersField.isAccessible(); + if (doForceAccess) { + modifiersField.setAccessible(true); + } + try { + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } finally { + if (doForceAccess) { + modifiersField.setAccessible(false); + } + } + } + } catch (final NoSuchFieldException | IllegalAccessException e) { + if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_12)) { + throw new UnsupportedOperationException( + "In java 12+ final cannot be removed.", + e + ); + } + // else no exception is thrown because we can modify final. + } } /** - * Writes a named {@code public static} {@link Field}. Superclasses will be considered. + * Writes a {@code public} {@link Field}. Only the specified class will be considered. * - * @param cls - * {@link Class} on which the field is to be found + * @param target + * the object to reflect, must not be {@code null} * @param fieldName - * to write + * the field name to obtain * @param value * to set * @throws NullPointerException * if {@code target} is @{code null} * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, the field cannot be located or is - * not {@code static}, or {@code value} is not assignable + * if {@code fieldName} is {@code null}, blank or empty, or could not be found, + * or {@code value} is not assignable * @throws IllegalAccessException - * if the field is not {@code public} or is {@code final} + * if the field is not made accessible */ - public static void writeStaticField(final Class cls, final String fieldName, final Object value) throws IllegalAccessException { - writeStaticField(cls, fieldName, value, false); + public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { + writeDeclaredField(target, fieldName, value, false); } /** - * Writes a named {@code static} {@link Field}. Superclasses will be considered. + * Writes a {@code public} {@link Field}. Only the specified class will be considered. * - * @param cls - * {@link Class} on which the field is to be found + * @param target + * the object to reflect, must not be {@code null} * @param fieldName - * to write + * the field name to obtain * @param value * to set * @param forceAccess * whether to break scope restrictions using the * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only * match {@code public} fields. - * @throws NullPointerException - * if {@code cls} is {@code null} or the field cannot be located * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, the field not {@code static}, or {@code value} is not assignable + * if {@code fieldName} is {@code null}, blank or empty, or could not be found, + * or {@code value} is not assignable * @throws IllegalAccessException - * if the field is not made accessible or is {@code final} + * if the field is not made accessible */ - public static void writeStaticField(final Class cls, final String fieldName, final Object value, final boolean forceAccess) + public static void writeDeclaredField(final Object target, final String fieldName, final Object value, final boolean forceAccess) throws IllegalAccessException { - final Field field = getField(cls, fieldName, forceAccess); - Validate.notNull(field, "Cannot locate field %s on %s", fieldName, cls); + Objects.requireNonNull(target, "target"); + final Class cls = target.getClass(); + final Field field = getDeclaredField(cls, fieldName, forceAccess); + Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); // already forced access above, don't repeat it here: - writeStaticField(field, value, false); + writeField(field, target, value, false); } /** @@ -715,66 +724,6 @@ public class FieldUtils { field.set(target, value); } - /** - * Removes the final modifier from a {@link Field}. - * - * @param field - * to remove the final modifier - * @throws NullPointerException - * if the field is {@code null} - * @since 3.2 - */ - public static void removeFinalModifier(final Field field) { - removeFinalModifier(field, true); - } - - /** - * Removes the final modifier from a {@link Field}. - * - * @param field - * to remove the final modifier - * @param forceAccess - * whether to break scope restrictions using the - * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only - * match {@code public} fields. - * @throws NullPointerException - * if the field is {@code null} - * @deprecated As of Java 12, we can no longer drop the {@code final} modifier, thus - * rendering this method obsolete. The JDK discussion about this change can be found - * here: https://mail.openjdk.java.net/pipermail/core-libs-dev/2018-November/056486.html - * @since 3.3 - */ - @Deprecated - public static void removeFinalModifier(final Field field, final boolean forceAccess) { - Objects.requireNonNull(field, "field"); - - try { - if (Modifier.isFinal(field.getModifiers())) { - // Do all JREs implement Field with a private ivar called "modifiers"? - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - final boolean doForceAccess = forceAccess && !modifiersField.isAccessible(); - if (doForceAccess) { - modifiersField.setAccessible(true); - } - try { - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - } finally { - if (doForceAccess) { - modifiersField.setAccessible(false); - } - } - } - } catch (final NoSuchFieldException | IllegalAccessException e) { - if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_12)) { - throw new UnsupportedOperationException( - "In java 12+ final cannot be removed.", - e - ); - } - // else no exception is thrown because we can modify final. - } - } - /** * Writes a {@code public} {@link Field}. Superclasses will be considered. * @@ -828,52 +777,103 @@ public class FieldUtils { } /** - * Writes a {@code public} {@link Field}. Only the specified class will be considered. + * Writes a named {@code public static} {@link Field}. Superclasses will be considered. * - * @param target - * the object to reflect, must not be {@code null} + * @param cls + * {@link Class} on which the field is to be found * @param fieldName - * the field name to obtain + * to write * @param value * to set * @throws NullPointerException * if {@code target} is @{code null} * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, or could not be found, - * or {@code value} is not assignable + * if {@code fieldName} is {@code null}, blank or empty, the field cannot be located or is + * not {@code static}, or {@code value} is not assignable * @throws IllegalAccessException - * if the field is not made accessible + * if the field is not {@code public} or is {@code final} */ - public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { - writeDeclaredField(target, fieldName, value, false); + public static void writeStaticField(final Class cls, final String fieldName, final Object value) throws IllegalAccessException { + writeStaticField(cls, fieldName, value, false); } /** - * Writes a {@code public} {@link Field}. Only the specified class will be considered. + * Writes a named {@code static} {@link Field}. Superclasses will be considered. * - * @param target - * the object to reflect, must not be {@code null} + * @param cls + * {@link Class} on which the field is to be found * @param fieldName - * the field name to obtain + * to write * @param value * to set * @param forceAccess * whether to break scope restrictions using the * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only * match {@code public} fields. + * @throws NullPointerException + * if {@code cls} is {@code null} or the field cannot be located * @throws IllegalArgumentException - * if {@code fieldName} is {@code null}, blank or empty, or could not be found, - * or {@code value} is not assignable + * if {@code fieldName} is {@code null}, blank or empty, the field not {@code static}, or {@code value} is not assignable * @throws IllegalAccessException - * if the field is not made accessible + * if the field is not made accessible or is {@code final} */ - public static void writeDeclaredField(final Object target, final String fieldName, final Object value, final boolean forceAccess) + public static void writeStaticField(final Class cls, final String fieldName, final Object value, final boolean forceAccess) throws IllegalAccessException { - Objects.requireNonNull(target, "target"); - final Class cls = target.getClass(); - final Field field = getDeclaredField(cls, fieldName, forceAccess); - Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); + final Field field = getField(cls, fieldName, forceAccess); + Validate.notNull(field, "Cannot locate field %s on %s", fieldName, cls); // already forced access above, don't repeat it here: - writeField(field, target, value, false); + writeStaticField(field, value, false); + } + + /** + * Writes a {@code public static} {@link Field}. + * + * @param field + * to write + * @param value + * to set + * @throws NullPointerException + * if the field is {@code null} + * @throws IllegalArgumentException + * if the field is not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not {@code public} or is {@code final} + */ + public static void writeStaticField(final Field field, final Object value) throws IllegalAccessException { + writeStaticField(field, value, false); + } + + /** + * Writes a static {@link Field}. + * + * @param field + * to write + * @param value + * to set + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @throws NullPointerException + * if the field is {@code null} + * @throws IllegalArgumentException + * if the field is not {@code static}, or {@code value} is not assignable + * @throws IllegalAccessException + * if the field is not made accessible or is {@code final} + */ + public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException { + Objects.requireNonNull(field, "field"); + Validate.isTrue(MemberUtils.isStatic(field), "The field %s.%s is not static", field.getDeclaringClass().getName(), + field.getName()); + writeField(field, (Object) null, value, forceAccess); + } + + /** + * {@link FieldUtils} instances should NOT be constructed in standard programming. + *

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

+ */ + public FieldUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java b/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java index 3fed00e25..60aa3582c 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java @@ -25,17 +25,6 @@ import org.apache.commons.lang3.BooleanUtils; */ public class InheritanceUtils { - /** - * {@link InheritanceUtils} instances should NOT be constructed in standard programming. - * Instead, the class should be used as - * {@code MethodUtils.getAccessibleMethod(method)}. - * - *

This constructor is {@code public} to permit tools that require a JavaBean - * instance to operate.

- */ - public InheritanceUtils() { - } - /** * Returns the number of inheritance hops between two classes. * @@ -63,4 +52,15 @@ public class InheritanceUtils { d += distance(cParent, parent); return d > 0 ? d + 1 : -1; } + + /** + * {@link InheritanceUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code MethodUtils.getAccessibleMethod(method)}. + * + *

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

+ */ + public InheritanceUtils() { + } } diff --git a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java index ba98f13db..6dc74fed5 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java @@ -33,77 +33,47 @@ import org.apache.commons.lang3.ClassUtils; final class MemberUtils { // TODO extract an interface to implement compareParameterSets(...)? + /** + * A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8, + * providing a common representation for function signatures for Constructors and Methods. + */ + private static final class Executable { + private static Executable of(final Constructor constructor) { + return new Executable(constructor); + } + private static Executable of(final Method method) { + return new Executable(method); + } + + private final Class[] parameterTypes; + + private final boolean isVarArgs; + + private Executable(final Constructor constructor) { + parameterTypes = constructor.getParameterTypes(); + isVarArgs = constructor.isVarArgs(); + } + + private Executable(final Method method) { + parameterTypes = method.getParameterTypes(); + isVarArgs = method.isVarArgs(); + } + + public Class[] getParameterTypes() { + return parameterTypes; + } + + public boolean isVarArgs() { + return isVarArgs; + } + } + private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; /** Array of primitive number types ordered by "promotability" */ private static final Class[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE }; - /** - * Default access superclass workaround. - * - * When a {@code public} class has a default access superclass with {@code public} members, - * these members are accessible. Calling them from compiled code works fine. - * Unfortunately, on some JVMs, using reflection to invoke these members - * seems to (wrongly) prevent access even when the modifier is {@code public}. - * Calling {@code setAccessible(true)} solves the problem but will only work from - * sufficiently privileged code. Better workarounds would be gratefully - * accepted. - * @param obj the AccessibleObject to set as accessible - * @return a boolean indicating whether the accessibility of the object was set to true. - */ - static T setAccessibleWorkaround(final T obj) { - if (obj == null || obj.isAccessible()) { - return obj; - } - final Member m = (Member) obj; - if (!obj.isAccessible() && isPublic(m) && isPackageAccess(m.getDeclaringClass().getModifiers())) { - try { - obj.setAccessible(true); - return obj; - } catch (final SecurityException ignored) { - // ignore in favor of subsequent IllegalAccessException - } - } - return obj; - } - - /** - * Tests whether a given set of modifiers implies package access. - * @param modifiers to test - * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected - */ - static boolean isPackageAccess(final int modifiers) { - return (modifiers & ACCESS_TEST) == 0; - } - - /** - * Tests whether a {@link Member} is public. - * @param member Member to test - * @return {@code true} if {@code m} is public - */ - static boolean isPublic(final Member member) { - return member != null && Modifier.isPublic(member.getModifiers()); - } - - /** - * Tests whether a {@link Member} is static. - * @param member Member to test - * @return {@code true} if {@code m} is static - */ - static boolean isStatic(final Member member) { - return member != null && Modifier.isStatic(member.getModifiers()); - } - - /** - * Tests whether a {@link Member} is accessible. - * @param member Member to test - * @return {@code true} if {@code m} is accessible - */ - static boolean isAccessible(final Member member) { - return isPublic(member) && !member.isSynthetic(); - } - /** * Compares the relative fitness of two Constructors in terms of how well they * match a set of runtime parameter types, such that a list ordered @@ -156,52 +126,6 @@ final class MemberUtils { return Float.compare(leftCost, rightCost); } - /** - * Returns the sum of the object transformation cost for each class in the - * source argument list. - * @param srcArgs The source arguments - * @param executable The executable to calculate transformation costs for - * @return The total transformation cost - */ - private static float getTotalTransformationCost(final Class[] srcArgs, final Executable executable) { - final Class[] destArgs = executable.getParameterTypes(); - final boolean isVarArgs = executable.isVarArgs(); - - // "source" and "destination" are the actual and declared args respectively. - float totalCost = 0.0f; - final long normalArgsLen = isVarArgs ? destArgs.length - 1 : destArgs.length; - if (srcArgs.length < normalArgsLen) { - return Float.MAX_VALUE; - } - for (int i = 0; i < normalArgsLen; i++) { - totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]); - } - if (isVarArgs) { - // When isVarArgs is true, srcArgs and dstArgs may differ in length. - // There are two special cases to consider: - final boolean noVarArgsPassed = srcArgs.length < destArgs.length; - final boolean explicitArrayForVarargs = srcArgs.length == destArgs.length && srcArgs[srcArgs.length - 1] != null - && srcArgs[srcArgs.length - 1].isArray(); - - final float varArgsCost = 0.001f; - final Class destClass = destArgs[destArgs.length - 1].getComponentType(); - if (noVarArgsPassed) { - // When no varargs passed, the best match is the most generic matching type, not the most specific. - totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost; - } else if (explicitArrayForVarargs) { - final Class sourceClass = srcArgs[srcArgs.length - 1].getComponentType(); - totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost; - } else { - // This is typical varargs case. - for (int i = destArgs.length - 1; i < srcArgs.length; i++) { - final Class srcClass = srcArgs[i]; - totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost; - } - } - } - return totalCost; - } - /** * Gets the number of steps needed to turn the source class into * the destination class. This represents the number of steps in the object @@ -267,8 +191,59 @@ final class MemberUtils { return cost; } - static boolean isMatchingMethod(final Method method, final Class[] parameterTypes) { - return isMatchingExecutable(Executable.of(method), parameterTypes); + /** + * Returns the sum of the object transformation cost for each class in the + * source argument list. + * @param srcArgs The source arguments + * @param executable The executable to calculate transformation costs for + * @return The total transformation cost + */ + private static float getTotalTransformationCost(final Class[] srcArgs, final Executable executable) { + final Class[] destArgs = executable.getParameterTypes(); + final boolean isVarArgs = executable.isVarArgs(); + + // "source" and "destination" are the actual and declared args respectively. + float totalCost = 0.0f; + final long normalArgsLen = isVarArgs ? destArgs.length - 1 : destArgs.length; + if (srcArgs.length < normalArgsLen) { + return Float.MAX_VALUE; + } + for (int i = 0; i < normalArgsLen; i++) { + totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]); + } + if (isVarArgs) { + // When isVarArgs is true, srcArgs and dstArgs may differ in length. + // There are two special cases to consider: + final boolean noVarArgsPassed = srcArgs.length < destArgs.length; + final boolean explicitArrayForVarargs = srcArgs.length == destArgs.length && srcArgs[srcArgs.length - 1] != null + && srcArgs[srcArgs.length - 1].isArray(); + + final float varArgsCost = 0.001f; + final Class destClass = destArgs[destArgs.length - 1].getComponentType(); + if (noVarArgsPassed) { + // When no varargs passed, the best match is the most generic matching type, not the most specific. + totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost; + } else if (explicitArrayForVarargs) { + final Class sourceClass = srcArgs[srcArgs.length - 1].getComponentType(); + totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost; + } else { + // This is typical varargs case. + for (int i = destArgs.length - 1; i < srcArgs.length; i++) { + final Class srcClass = srcArgs[i]; + totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost; + } + } + } + return totalCost; + } + + /** + * Tests whether a {@link Member} is accessible. + * @param member Member to test + * @return {@code true} if {@code m} is accessible + */ + static boolean isAccessible(final Member member) { + return isPublic(member) && !member.isSynthetic(); } static boolean isMatchingConstructor(final Constructor method, final Class[] parameterTypes) { @@ -300,39 +275,64 @@ final class MemberUtils { return false; } + static boolean isMatchingMethod(final Method method, final Class[] parameterTypes) { + return isMatchingExecutable(Executable.of(method), parameterTypes); + } + /** - * A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8, - * providing a common representation for function signatures for Constructors and Methods. + * Tests whether a given set of modifiers implies package access. + * @param modifiers to test + * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected */ - private static final class Executable { - private final Class[] parameterTypes; - private final boolean isVarArgs; + static boolean isPackageAccess(final int modifiers) { + return (modifiers & ACCESS_TEST) == 0; + } - private static Executable of(final Method method) { - return new Executable(method); - } + /** + * Tests whether a {@link Member} is public. + * @param member Member to test + * @return {@code true} if {@code m} is public + */ + static boolean isPublic(final Member member) { + return member != null && Modifier.isPublic(member.getModifiers()); + } - private static Executable of(final Constructor constructor) { - return new Executable(constructor); - } + /** + * Tests whether a {@link Member} is static. + * @param member Member to test + * @return {@code true} if {@code m} is static + */ + static boolean isStatic(final Member member) { + return member != null && Modifier.isStatic(member.getModifiers()); + } - private Executable(final Method method) { - parameterTypes = method.getParameterTypes(); - isVarArgs = method.isVarArgs(); - } - - private Executable(final Constructor constructor) { - parameterTypes = constructor.getParameterTypes(); - isVarArgs = constructor.isVarArgs(); - } - - public Class[] getParameterTypes() { - return parameterTypes; - } - - public boolean isVarArgs() { - return isVarArgs; - } + /** + * Default access superclass workaround. + * + * When a {@code public} class has a default access superclass with {@code public} members, + * these members are accessible. Calling them from compiled code works fine. + * Unfortunately, on some JVMs, using reflection to invoke these members + * seems to (wrongly) prevent access even when the modifier is {@code public}. + * Calling {@code setAccessible(true)} solves the problem but will only work from + * sufficiently privileged code. Better workarounds would be gratefully + * accepted. + * @param obj the AccessibleObject to set as accessible + * @return a boolean indicating whether the accessibility of the object was set to true. + */ + static T setAccessibleWorkaround(final T obj) { + if (obj == null || obj.isAccessible()) { + return obj; + } + final Member m = (Member) obj; + if (!obj.isAccessible() && isPublic(m) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + obj.setAccessible(true); + return obj; + } catch (final SecurityException ignored) { + // ignore in favor of subsequent IllegalAccessException + } + } + return obj; } } diff --git a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java index 3cf99834b..1fae8bcad 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java @@ -64,454 +64,34 @@ public class MethodUtils { private static final Comparator METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString); /** - * {@link MethodUtils} instances should NOT be constructed in standard programming. - * Instead, the class should be used as - * {@code MethodUtils.getAccessibleMethod(method)}. - * - *

This constructor is {@code public} to permit tools that require a JavaBean - * instance to operate.

+ * Returns the aggregate number of inheritance hops between assignable argument class types. Returns -1 + * if the arguments aren't assignable. Fills a specific purpose for getMatchingMethod and is not generalized. + * @param fromClassArray the Class array to calculate the distance from. + * @param toClassArray the Class array to calculate the distance to. + * @return the aggregate number of inheritance hops between assignable argument class types. */ - public MethodUtils() { - } + private static int distance(final Class[] fromClassArray, final Class[] toClassArray) { + int answer = 0; - /** - * Invokes a named method without parameters. - * - *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

- * - *

This is a convenient wrapper for - * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. - *

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - * - * @since 3.4 - */ - public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); - } - - /** - * Invokes a named method without parameters. - * - *

This is a convenient wrapper for - * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. - *

- * - * @param object invoke method on this object - * @param forceAccess force access to invoke method even if it's not accessible - * @param methodName get method with this name - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - * - * @since 3.5 - */ - public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName) - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); - } - - /** - * Invokes a named method whose parameter type matches the object type. - * - *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

- * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} object - * would match a {@code boolean} primitive.

- * - *

This is a convenient wrapper for - * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. - *

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - * @throws NullPointerException if the object or method name are {@code null} - */ - public static Object invokeMethod(final Object object, final String methodName, - Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - return invokeMethod(object, methodName, args, ClassUtils.toClass(args)); - } - - /** - * Invokes a named method whose parameter type matches the object type. - * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} object - * would match a {@code boolean} primitive.

- * - *

This is a convenient wrapper for - * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. - *

- * - * @param object invoke method on this object - * @param forceAccess force access to invoke method even if it's not accessible - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - * @throws NullPointerException if the object or method name are {@code null} - * @since 3.5 - */ - public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, - Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - return invokeMethod(object, forceAccess, methodName, args, ClassUtils.toClass(args)); - } - - /** - * Invokes a named method whose parameter type matches the object type. - * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} object - * would match a {@code boolean} primitive.

- * - * @param object invoke method on this object - * @param forceAccess force access to invoke method even if it's not accessible - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - * @throws NullPointerException if the object or method name are {@code null} - * @since 3.5 - */ - public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, Object[] args, Class[] parameterTypes) - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Objects.requireNonNull(object, "object"); - parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); - args = ArrayUtils.nullToEmpty(args); - - final String messagePrefix; - final Method method; - - final Class cls = object.getClass(); - if (forceAccess) { - messagePrefix = "No such method: "; - method = getMatchingMethod(cls, methodName, parameterTypes); - if (method != null && !method.isAccessible()) { - method.setAccessible(true); + if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) { + return -1; + } + for (int offset = 0; offset < fromClassArray.length; offset++) { + // Note InheritanceUtils.distance() uses different scoring system. + final Class aClass = fromClassArray[offset]; + final Class toClass = toClassArray[offset]; + if (aClass == null || aClass.equals(toClass)) { + continue; + } + if (ClassUtils.isAssignable(aClass, toClass, true) + && !ClassUtils.isAssignable(aClass, toClass, false)) { + answer++; + } else { + answer = answer + 2; } - } else { - messagePrefix = "No such accessible method: "; - method = getMatchingAccessibleMethod(cls, methodName, parameterTypes); } - if (method == null) { - throw new NoSuchMethodException(messagePrefix + methodName + "() on object: " + cls.getName()); - } - args = toVarArgs(method, args); - - return method.invoke(object, args); - } - - /** - * Invokes a named method whose parameter type matches the object type. - * - *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

- * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} object - * would match a {@code boolean} primitive.

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the method invoked - * @throws IllegalAccessException if the requested method is not accessible via reflection - */ - public static Object invokeMethod(final Object object, final String methodName, - final Object[] args, final Class[] parameterTypes) - throws NoSuchMethodException, IllegalAccessException, - InvocationTargetException { - return invokeMethod(object, false, methodName, args, parameterTypes); - } - - /** - * Invokes a method whose parameter types match exactly the object - * types. - * - *

This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod}(Class, String, Class[])}.

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * - * @since 3.4 - */ - public static Object invokeExactMethod(final Object object, final String methodName) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); - } - - /** - * Invokes a method with no parameters. - * - *

This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod}(Class, String, Class[])}.

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @throws NullPointerException if the object or method name are {@code null} - */ - public static Object invokeExactMethod(final Object object, final String methodName, - Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - return invokeExactMethod(object, methodName, args, ClassUtils.toClass(args)); - } - - /** - * Invokes a method whose parameter types match exactly the parameter - * types given. - * - *

This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod(Class, String, Class[])}.

- * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat {@code null} as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @throws NullPointerException if the object or method name are {@code null} - */ - public static Object invokeExactMethod(final Object object, final String methodName, Object[] args, Class[] parameterTypes) - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Objects.requireNonNull(object, "object"); - args = ArrayUtils.nullToEmpty(args); - parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); - final Class cls = object.getClass(); - final Method method = getAccessibleMethod(cls, methodName, parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + cls.getName()); - } - return method.invoke(object, args); - } - - /** - * Invokes a {@code static} method whose parameter types match exactly the parameter - * types given. - * - *

This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod(Class, String, Class[])}.

- * - * @param cls invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat {@code null} as empty array - * @param parameterTypes match these parameters - treat {@code null} as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeExactStaticMethod(final Class cls, final String methodName, - Object[] args, Class[] parameterTypes) - throws NoSuchMethodException, IllegalAccessException, - InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); - final Method method = getAccessibleMethod(cls, methodName, parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " - + methodName + "() on class: " + cls.getName()); - } - return method.invoke(null, args); - } - - /** - * Invokes a named {@code static} method whose parameter type matches the object type. - * - *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

- * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} class - * would match a {@code boolean} primitive.

- * - *

This is a convenient wrapper for - * {@link #invokeStaticMethod(Class, String, Object[], Class[])}. - *

- * - * @param cls invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat {@code null} as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeStaticMethod(final Class cls, final String methodName, - Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - return invokeStaticMethod(cls, methodName, args, ClassUtils.toClass(args)); - } - - /** - * Invokes a named {@code static} method whose parameter type matches the object type. - * - *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

- * - *

This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a {@link Boolean} class - * would match a {@code boolean} primitive.

- * - * @param cls invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat {@code null} as empty array - * @param parameterTypes match these parameters - treat {@code null} as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeStaticMethod(final Class cls, final String methodName, - Object[] args, Class[] parameterTypes) - throws NoSuchMethodException, IllegalAccessException, - InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); - final Method method = getMatchingAccessibleMethod(cls, methodName, - parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " - + methodName + "() on class: " + cls.getName()); - } - args = toVarArgs(method, args); - return method.invoke(null, args); - } - - private static Object[] toVarArgs(final Method method, Object[] args) { - if (method.isVarArgs()) { - final Class[] methodParameterTypes = method.getParameterTypes(); - args = getVarArgs(args, methodParameterTypes); - } - return args; - } - - /** - * Given an arguments array passed to a varargs method, return an array of arguments in the canonical form, - * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type. - * - * @param args the array of arguments passed to the varags method - * @param methodParameterTypes the declared array of method parameter types - * @return an array of the variadic arguments passed to the method - * @since 3.5 - */ - static Object[] getVarArgs(final Object[] args, final Class[] methodParameterTypes) { - if (args.length == methodParameterTypes.length && (args[args.length - 1] == null || - args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) { - // The args array is already in the canonical form for the method. - return args; - } - - // Construct a new array matching the method's declared parameter types. - final Object[] newArgs = new Object[methodParameterTypes.length]; - - // Copy the normal (non-varargs) parameters - System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); - - // Construct a new array for the variadic parameters - final Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); - final int varArgLength = args.length - methodParameterTypes.length + 1; - - Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength); - // Copy the variadic arguments into the varargs array. - System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); - - if (varArgComponentType.isPrimitive()) { - // unbox from wrapper type to primitive type - varArgsArray = ArrayUtils.toPrimitive(varArgsArray); - } - - // Store the varargs array in the last position of the array to return - newArgs[methodParameterTypes.length - 1] = varArgsArray; - - // Return the canonical varargs array. - return newArgs; - } - - /** - * Invokes a {@code static} method whose parameter types match exactly the object - * types. - * - *

This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod(Class, String, Class[])}.

- * - * @param cls invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat {@code null} as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeExactStaticMethod(final Class cls, final String methodName, - Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException { - args = ArrayUtils.nullToEmpty(args); - return invokeExactStaticMethod(cls, methodName, args, ClassUtils.toClass(args)); + return answer; } /** @@ -567,32 +147,6 @@ public class MethodUtils { return method; } - /** - * Returns an accessible method (that is, one that can be invoked via - * reflection) by scanning through the superclasses. If no such method - * can be found, return {@code null}. - * - * @param cls Class to be checked - * @param methodName Method name of the method we wish to call - * @param parameterTypes The parameter type signatures - * @return the accessible method or {@code null} if not found - */ - private static Method getAccessibleMethodFromSuperclass(final Class cls, - final String methodName, final Class... parameterTypes) { - Class parentClass = cls.getSuperclass(); - while (parentClass != null) { - if (ClassUtils.isPublic(parentClass)) { - try { - return parentClass.getMethod(methodName, parameterTypes); - } catch (final NoSuchMethodException e) { - return null; - } - } - parentClass = parentClass.getSuperclass(); - } - return null; - } - /** * Returns an accessible method (that is, one that can be invoked via * reflection) that implements the specified method, by scanning through @@ -641,6 +195,118 @@ public class MethodUtils { return null; } + /** + * Returns an accessible method (that is, one that can be invoked via + * reflection) by scanning through the superclasses. If no such method + * can be found, return {@code null}. + * + * @param cls Class to be checked + * @param methodName Method name of the method we wish to call + * @param parameterTypes The parameter type signatures + * @return the accessible method or {@code null} if not found + */ + private static Method getAccessibleMethodFromSuperclass(final Class cls, + final String methodName, final Class... parameterTypes) { + Class parentClass = cls.getSuperclass(); + while (parentClass != null) { + if (ClassUtils.isPublic(parentClass)) { + try { + return parentClass.getMethod(methodName, parameterTypes); + } catch (final NoSuchMethodException e) { + return null; + } + } + parentClass = parentClass.getSuperclass(); + } + return null; + } + + /** + * Gets a combination of {@link ClassUtils#getAllSuperclasses(Class)} and + * {@link ClassUtils#getAllInterfaces(Class)}, one from superclasses, one + * from interfaces, and so on in a breadth first way. + * + * @param cls the class to look up, may be {@code null} + * @return the combined {@link List} of superclasses and interfaces in order + * going up from this one + * {@code null} if null input + */ + private static List> getAllSuperclassesAndInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final List> allSuperClassesAndInterfaces = new ArrayList<>(); + final List> allSuperclasses = ClassUtils.getAllSuperclasses(cls); + int superClassIndex = 0; + final List> allInterfaces = ClassUtils.getAllInterfaces(cls); + int interfaceIndex = 0; + while (interfaceIndex < allInterfaces.size() || + superClassIndex < allSuperclasses.size()) { + final Class acls; + if (interfaceIndex >= allInterfaces.size()) { + acls = allSuperclasses.get(superClassIndex++); + } else if (superClassIndex >= allSuperclasses.size() || !(superClassIndex < interfaceIndex)) { + acls = allInterfaces.get(interfaceIndex++); + } else { + acls = allSuperclasses.get(superClassIndex++); + } + allSuperClassesAndInterfaces.add(acls); + } + return allSuperClassesAndInterfaces; + } + + /** + * Gets the annotation object with the given annotation type that is present on the given method + * or optionally on any equivalent method in super classes and interfaces. Returns null if the annotation + * type was not present. + * + *

Stops searching for an annotation once the first annotation of the specified type has been + * found. Additional annotations of the specified type will be silently ignored.

+ * @param + * the annotation type + * @param method + * the {@link Method} to query + * @param annotationCls + * the {@link Annotation} to check if is present on the method + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class is performed + * if the annotation was not directly present + * @param ignoreAccess + * determines if underlying method has to be accessible + * @return the first matching annotation, or {@code null} if not found + * @throws NullPointerException if either the method or annotation class is {@code null} + * @since 3.6 + */ + public static A getAnnotation(final Method method, final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { + + Objects.requireNonNull(method, "method"); + Objects.requireNonNull(annotationCls, "annotationCls"); + if (!ignoreAccess && !MemberUtils.isAccessible(method)) { + return null; + } + + A annotation = method.getAnnotation(annotationCls); + + if (annotation == null && searchSupers) { + final Class mcls = method.getDeclaringClass(); + final List> classes = getAllSuperclassesAndInterfaces(mcls); + for (final Class acls : classes) { + final Method equivalentMethod = ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes()) + : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes()); + if (equivalentMethod != null) { + annotation = equivalentMethod.getAnnotation(annotationCls); + if (annotation != null) { + break; + } + } + } + } + + return annotation; + } + /** * Finds an accessible method that matches the given name and has compatible parameters. * Compatible parameters mean that every method parameter is assignable from @@ -770,34 +436,81 @@ public class MethodUtils { } /** - * Returns the aggregate number of inheritance hops between assignable argument class types. Returns -1 - * if the arguments aren't assignable. Fills a specific purpose for getMatchingMethod and is not generalized. - * @param fromClassArray the Class array to calculate the distance from. - * @param toClassArray the Class array to calculate the distance to. - * @return the aggregate number of inheritance hops between assignable argument class types. + * Gets all class level public methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a method to be matched + * @return a list of Methods (possibly empty). + * @throws NullPointerException + * if the class or annotation are {@code null} + * @since 3.4 */ - private static int distance(final Class[] fromClassArray, final Class[] toClassArray) { - int answer = 0; + public static List getMethodsListWithAnnotation(final Class cls, final Class annotationCls) { + return getMethodsListWithAnnotation(cls, annotationCls, false, false); + } - if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) { - return -1; - } - for (int offset = 0; offset < fromClassArray.length; offset++) { - // Note InheritanceUtils.distance() uses different scoring system. - final Class aClass = fromClassArray[offset]; - final Class toClass = toClassArray[offset]; - if (aClass == null || aClass.equals(toClass)) { - continue; - } - if (ClassUtils.isAssignable(aClass, toClass, true) - && !ClassUtils.isAssignable(aClass, toClass, false)) { - answer++; - } else { - answer = answer + 2; - } - } + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link Annotation} that must be present on a method to be matched + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non-public methods should be considered + * @return a list of Methods (possibly empty). + * @throws NullPointerException if either the class or annotation class is {@code null} + * @since 3.6 + */ + public static List getMethodsListWithAnnotation(final Class cls, + final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { - return answer; + Objects.requireNonNull(cls, "cls"); + Objects.requireNonNull(annotationCls, "annotationCls"); + final List> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>(); + classes.add(0, cls); + final List annotatedMethods = new ArrayList<>(); + classes.forEach(acls -> { + final Method[] methods = ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods(); + Stream.of(methods).filter(method -> method.isAnnotationPresent(annotationCls)).forEachOrdered(annotatedMethods::add); + }); + return annotatedMethods; + } + + /** + * Gets all class level public methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched + * @return an array of Methods (possibly empty). + * @throws NullPointerException if the class or annotation are {@code null} + * @since 3.4 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls) { + return getMethodsWithAnnotation(cls, annotationCls, false, false); + } + + /** + * Gets all methods of the given class that are annotated with the given annotation. + * @param cls + * the {@link Class} to query + * @param annotationCls + * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched + * @param searchSupers + * determines if a lookup in the entire inheritance hierarchy of the given class should be performed + * @param ignoreAccess + * determines if non-public methods should be considered + * @return an array of Methods (possibly empty). + * @throws NullPointerException if the class or annotation are {@code null} + * @since 3.6 + */ + public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls, + final boolean searchSupers, final boolean ignoreAccess) { + return getMethodsListWithAnnotation(cls, annotationCls, searchSupers, ignoreAccess).toArray(ArrayUtils.EMPTY_METHOD_ARRAY); } /** @@ -846,166 +559,453 @@ public class MethodUtils { } /** - * Gets all class level public methods of the given class that are annotated with the given annotation. - * @param cls - * the {@link Class} to query - * @param annotationCls - * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched - * @return an array of Methods (possibly empty). - * @throws NullPointerException if the class or annotation are {@code null} + * Given an arguments array passed to a varargs method, return an array of arguments in the canonical form, + * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type. + * + * @param args the array of arguments passed to the varags method + * @param methodParameterTypes the declared array of method parameter types + * @return an array of the variadic arguments passed to the method + * @since 3.5 + */ + static Object[] getVarArgs(final Object[] args, final Class[] methodParameterTypes) { + if (args.length == methodParameterTypes.length && (args[args.length - 1] == null || + args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) { + // The args array is already in the canonical form for the method. + return args; + } + + // Construct a new array matching the method's declared parameter types. + final Object[] newArgs = new Object[methodParameterTypes.length]; + + // Copy the normal (non-varargs) parameters + System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); + + // Construct a new array for the variadic parameters + final Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); + final int varArgLength = args.length - methodParameterTypes.length + 1; + + Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength); + // Copy the variadic arguments into the varargs array. + System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); + + if (varArgComponentType.isPrimitive()) { + // unbox from wrapper type to primitive type + varArgsArray = ArrayUtils.toPrimitive(varArgsArray); + } + + // Store the varargs array in the last position of the array to return + newArgs[methodParameterTypes.length - 1] = varArgsArray; + + // Return the canonical varargs array. + return newArgs; + } + + /** + * Invokes a method whose parameter types match exactly the object + * types. + * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + * * @since 3.4 */ - public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls) { - return getMethodsWithAnnotation(cls, annotationCls, false, false); + public static Object invokeExactMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); } /** - * Gets all class level public methods of the given class that are annotated with the given annotation. - * @param cls - * the {@link Class} to query - * @param annotationCls - * the {@link Annotation} that must be present on a method to be matched - * @return a list of Methods (possibly empty). - * @throws NullPointerException - * if the class or annotation are {@code null} - * @since 3.4 - */ - public static List getMethodsListWithAnnotation(final Class cls, final Class annotationCls) { - return getMethodsListWithAnnotation(cls, annotationCls, false, false); - } - - /** - * Gets all methods of the given class that are annotated with the given annotation. - * @param cls - * the {@link Class} to query - * @param annotationCls - * the {@link java.lang.annotation.Annotation} that must be present on a method to be matched - * @param searchSupers - * determines if a lookup in the entire inheritance hierarchy of the given class should be performed - * @param ignoreAccess - * determines if non-public methods should be considered - * @return an array of Methods (possibly empty). - * @throws NullPointerException if the class or annotation are {@code null} - * @since 3.6 - */ - public static Method[] getMethodsWithAnnotation(final Class cls, final Class annotationCls, - final boolean searchSupers, final boolean ignoreAccess) { - return getMethodsListWithAnnotation(cls, annotationCls, searchSupers, ignoreAccess).toArray(ArrayUtils.EMPTY_METHOD_ARRAY); - } - - /** - * Gets all methods of the given class that are annotated with the given annotation. - * @param cls - * the {@link Class} to query - * @param annotationCls - * the {@link Annotation} that must be present on a method to be matched - * @param searchSupers - * determines if a lookup in the entire inheritance hierarchy of the given class should be performed - * @param ignoreAccess - * determines if non-public methods should be considered - * @return a list of Methods (possibly empty). - * @throws NullPointerException if either the class or annotation class is {@code null} - * @since 3.6 - */ - public static List getMethodsListWithAnnotation(final Class cls, - final Class annotationCls, - final boolean searchSupers, final boolean ignoreAccess) { - - Objects.requireNonNull(cls, "cls"); - Objects.requireNonNull(annotationCls, "annotationCls"); - final List> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>(); - classes.add(0, cls); - final List annotatedMethods = new ArrayList<>(); - classes.forEach(acls -> { - final Method[] methods = ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods(); - Stream.of(methods).filter(method -> method.isAnnotationPresent(annotationCls)).forEachOrdered(annotatedMethods::add); - }); - return annotatedMethods; - } - - /** - * Gets the annotation object with the given annotation type that is present on the given method - * or optionally on any equivalent method in super classes and interfaces. Returns null if the annotation - * type was not present. + * Invokes a method with no parameters. * - *

Stops searching for an annotation once the first annotation of the specified type has been - * found. Additional annotations of the specified type will be silently ignored.

- * @param
- * the annotation type - * @param method - * the {@link Method} to query - * @param annotationCls - * the {@link Annotation} to check if is present on the method - * @param searchSupers - * determines if a lookup in the entire inheritance hierarchy of the given class is performed - * if the annotation was not directly present - * @param ignoreAccess - * determines if underlying method has to be accessible - * @return the first matching annotation, or {@code null} if not found - * @throws NullPointerException if either the method or annotation class is {@code null} - * @since 3.6 + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod}(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + * @throws NullPointerException if the object or method name are {@code null} */ - public static
A getAnnotation(final Method method, final Class annotationCls, - final boolean searchSupers, final boolean ignoreAccess) { - - Objects.requireNonNull(method, "method"); - Objects.requireNonNull(annotationCls, "annotationCls"); - if (!ignoreAccess && !MemberUtils.isAccessible(method)) { - return null; - } - - A annotation = method.getAnnotation(annotationCls); - - if (annotation == null && searchSupers) { - final Class mcls = method.getDeclaringClass(); - final List> classes = getAllSuperclassesAndInterfaces(mcls); - for (final Class acls : classes) { - final Method equivalentMethod = ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes()) - : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes()); - if (equivalentMethod != null) { - annotation = equivalentMethod.getAnnotation(annotationCls); - if (annotation != null) { - break; - } - } - } - } - - return annotation; + public static Object invokeExactMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + return invokeExactMethod(object, methodName, args, ClassUtils.toClass(args)); } /** - * Gets a combination of {@link ClassUtils#getAllSuperclasses(Class)} and - * {@link ClassUtils#getAllInterfaces(Class)}, one from superclasses, one - * from interfaces, and so on in a breadth first way. + * Invokes a method whose parameter types match exactly the parameter + * types given. * - * @param cls the class to look up, may be {@code null} - * @return the combined {@link List} of superclasses and interfaces in order - * going up from this one - * {@code null} if null input + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + * @throws NullPointerException if the object or method name are {@code null} */ - private static List> getAllSuperclassesAndInterfaces(final Class cls) { - if (cls == null) { - return null; + public static Object invokeExactMethod(final Object object, final String methodName, Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Objects.requireNonNull(object, "object"); + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Class cls = object.getClass(); + final Method method = getAccessibleMethod(cls, methodName, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + cls.getName()); + } + return method.invoke(object, args); + } + + /** + * Invokes a {@code static} method whose parameter types match exactly the object + * types. + * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + return invokeExactStaticMethod(cls, methodName, args, ClassUtils.toClass(args)); + } + + /** + * Invokes a {@code static} method whose parameter types match exactly the parameter + * types given. + * + *

This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getAccessibleMethod(cls, methodName, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + return method.invoke(null, args); + } + + /** + * Invokes a named method without parameters. + * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + * Invokes a named method whose parameter type matches the object type. + * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} object + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * @throws NullPointerException if the object or method name are {@code null} + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + return invokeMethod(object, forceAccess, methodName, args, ClassUtils.toClass(args)); + } + + /** + * Invokes a named method whose parameter type matches the object type. + * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} object + * would match a {@code boolean} primitive.

+ * + * @param object invoke method on this object + * @param forceAccess force access to invoke method even if it's not accessible + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * @throws NullPointerException if the object or method name are {@code null} + * @since 3.5 + */ + public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Objects.requireNonNull(object, "object"); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + args = ArrayUtils.nullToEmpty(args); + + final String messagePrefix; + final Method method; + + final Class cls = object.getClass(); + if (forceAccess) { + messagePrefix = "No such method: "; + method = getMatchingMethod(cls, methodName, parameterTypes); + if (method != null && !method.isAccessible()) { + method.setAccessible(true); + } + } else { + messagePrefix = "No such accessible method: "; + method = getMatchingAccessibleMethod(cls, methodName, parameterTypes); } - final List> allSuperClassesAndInterfaces = new ArrayList<>(); - final List> allSuperclasses = ClassUtils.getAllSuperclasses(cls); - int superClassIndex = 0; - final List> allInterfaces = ClassUtils.getAllInterfaces(cls); - int interfaceIndex = 0; - while (interfaceIndex < allInterfaces.size() || - superClassIndex < allSuperclasses.size()) { - final Class acls; - if (interfaceIndex >= allInterfaces.size()) { - acls = allSuperclasses.get(superClassIndex++); - } else if (superClassIndex >= allSuperclasses.size() || !(superClassIndex < interfaceIndex)) { - acls = allInterfaces.get(interfaceIndex++); - } else { - acls = allSuperclasses.get(superClassIndex++); - } - allSuperClassesAndInterfaces.add(acls); + if (method == null) { + throw new NoSuchMethodException(messagePrefix + methodName + "() on object: " + cls.getName()); } - return allSuperClassesAndInterfaces; + args = toVarArgs(method, args); + + return method.invoke(object, args); + } + + /** + * Invokes a named method without parameters. + * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * + * @since 3.4 + */ + public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); + } + + /** + * Invokes a named method whose parameter type matches the object type. + * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} object + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. + *

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + * @throws NullPointerException if the object or method name are {@code null} + */ + public static Object invokeMethod(final Object object, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + return invokeMethod(object, methodName, args, ClassUtils.toClass(args)); + } + + /** + * Invokes a named method whose parameter type matches the object type. + * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} object + * would match a {@code boolean} primitive.

+ * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the method invoked + * @throws IllegalAccessException if the requested method is not accessible via reflection + */ + public static Object invokeMethod(final Object object, final String methodName, + final Object[] args, final Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + return invokeMethod(object, false, methodName, args, parameterTypes); + } + + /** + * Invokes a named {@code static} method whose parameter type matches the object type. + * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} class + * would match a {@code boolean} primitive.

+ * + *

This is a convenient wrapper for + * {@link #invokeStaticMethod(Class, String, Object[], Class[])}. + *

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object... args) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + return invokeStaticMethod(cls, methodName, args, ClassUtils.toClass(args)); + } + + /** + * Invokes a named {@code static} method whose parameter type matches the object type. + * + *

This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.

+ * + *

This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a {@link Boolean} class + * would match a {@code boolean} primitive.

+ * + * @param cls invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat {@code null} as empty array + * @param parameterTypes match these parameters - treat {@code null} as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod(final Class cls, final String methodName, + Object[] args, Class[] parameterTypes) + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + args = ArrayUtils.nullToEmpty(args); + parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); + final Method method = getMatchingAccessibleMethod(cls, methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + cls.getName()); + } + args = toVarArgs(method, args); + return method.invoke(null, args); + } + + private static Object[] toVarArgs(final Method method, Object[] args) { + if (method.isVarArgs()) { + final Class[] methodParameterTypes = method.getParameterTypes(); + args = getVarArgs(args, methodParameterTypes); + } + return args; + } + + /** + * {@link MethodUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as + * {@code MethodUtils.getAccessibleMethod(method)}. + * + *

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

+ */ + public MethodUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java b/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java index e3ea475e7..fc00dce57 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java +++ b/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java @@ -107,6 +107,11 @@ public abstract class TypeLiteral implements Typed { return TypeUtils.equals(value, other.value); } + @Override + public Type getType() { + return value; + } + @Override public int hashCode() { return 37 << 4 | value.hashCode(); @@ -116,9 +121,4 @@ public abstract class TypeLiteral implements Typed { public String toString() { return toString; } - - @Override - public Type getType() { - return value; - } } diff --git a/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java b/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java index a80cd60ec..c41afef37 100644 --- a/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java +++ b/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java @@ -71,6 +71,24 @@ public class CompositeFormat extends Format { return formatter.format(obj, toAppendTo, pos); } + /** + * Provides access to the parser Format implementation. + * + * @return formatter Format implementation + */ + public Format getFormatter() { + return this.formatter; + } + + /** + * Provides access to the parser Format implementation. + * + * @return parser Format implementation + */ + public Format getParser() { + return this.parser; + } + /** * Uses the parser Format instance. * @@ -86,24 +104,6 @@ public class CompositeFormat extends Format { return parser.parseObject(source, pos); } - /** - * Provides access to the parser Format implementation. - * - * @return parser Format implementation - */ - public Format getParser() { - return this.parser; - } - - /** - * Provides access to the parser Format implementation. - * - * @return formatter Format implementation - */ - public Format getFormatter() { - return this.formatter; - } - /** * Utility method to parse and then reformat a String. * diff --git a/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java index df8a19cec..ff4e77888 100644 --- a/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java +++ b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java @@ -111,17 +111,6 @@ public class ExtendedMessageFormat extends MessageFormat { this(pattern, locale, null); } - /** - * Create a new ExtendedMessageFormat for the default locale. - * - * @param pattern the pattern to use, not null - * @param registry the registry of format factories, may be null - * @throws IllegalArgumentException in case of a bad pattern. - */ - public ExtendedMessageFormat(final String pattern, final Map registry) { - this(pattern, Locale.getDefault(), registry); - } - /** * Create a new ExtendedMessageFormat. * @@ -138,11 +127,48 @@ public class ExtendedMessageFormat extends MessageFormat { } /** - * {@inheritDoc} + * Create a new ExtendedMessageFormat for the default locale. + * + * @param pattern the pattern to use, not null + * @param registry the registry of format factories, may be null + * @throws IllegalArgumentException in case of a bad pattern. */ - @Override - public String toPattern() { - return toPattern; + public ExtendedMessageFormat(final String pattern, final Map registry) { + this(pattern, Locale.getDefault(), registry); + } + + /** + * Consume a quoted string, adding it to {@code appendTo} if + * specified. + * + * @param pattern pattern to parse + * @param pos current parse position + * @param appendTo optional StringBuilder to append + * @return {@code appendTo} + */ + private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos, + final StringBuilder appendTo) { + assert pattern.toCharArray()[pos.getIndex()] == QUOTE : + "Quoted string must start with quote character"; + + // handle quote character at the beginning of the string + if (appendTo != null) { + appendTo.append(QUOTE); + } + next(pos); + + final int start = pos.getIndex(); + final char[] c = pattern.toCharArray(); + for (int i = pos.getIndex(); i < pattern.length(); i++) { + if (c[pos.getIndex()] == QUOTE) { + next(pos); + return appendTo == null ? null : appendTo.append(c, start, + pos.getIndex() - start); + } + next(pos); + } + throw new IllegalArgumentException( + "Unterminated quoted string at position " + start); } /** @@ -218,49 +244,15 @@ public class ExtendedMessageFormat extends MessageFormat { } /** - * Throws UnsupportedOperationException - see class Javadoc for details. - * - * @param formatElementIndex format element index - * @param newFormat the new format - * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat + * Learn whether the specified Collection contains non-null elements. + * @param coll to check + * @return {@code true} if some Object was found, {@code false} otherwise. */ - @Override - public void setFormat(final int formatElementIndex, final Format newFormat) { - throw new UnsupportedOperationException(); - } - - /** - * Throws UnsupportedOperationException - see class Javadoc for details. - * - * @param argumentIndex argument index - * @param newFormat the new format - * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat - */ - @Override - public void setFormatByArgumentIndex(final int argumentIndex, final Format newFormat) { - throw new UnsupportedOperationException(); - } - - /** - * Throws UnsupportedOperationException - see class Javadoc for details. - * - * @param newFormats new formats - * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat - */ - @Override - public void setFormats(final Format[] newFormats) { - throw new UnsupportedOperationException(); - } - - /** - * Throws UnsupportedOperationException - see class Javadoc for details. - * - * @param newFormats new formats - * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat - */ - @Override - public void setFormatsByArgumentIndex(final Format[] newFormats) { - throw new UnsupportedOperationException(); + private boolean containsElements(final Collection coll) { + if (coll == null || coll.isEmpty()) { + return false; + } + return coll.stream().anyMatch(Objects::nonNull); } /** @@ -290,17 +282,6 @@ public class ExtendedMessageFormat extends MessageFormat { return !ObjectUtils.notEqual(registry, rhs.registry); } - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - int result = super.hashCode(); - result = HASH_SEED * result + Objects.hashCode(registry); - result = HASH_SEED * result + Objects.hashCode(toPattern); - return result; - } - /** * Gets a custom format from a format description. * @@ -325,79 +306,24 @@ public class ExtendedMessageFormat extends MessageFormat { } /** - * Read the argument index from the current format element + * Consume quoted string only * * @param pattern pattern to parse * @param pos current parse position - * @return argument index */ - private int readArgumentIndex(final String pattern, final ParsePosition pos) { - final int start = pos.getIndex(); - seekNonWs(pattern, pos); - final StringBuilder result = new StringBuilder(); - boolean error = false; - for (; !error && pos.getIndex() < pattern.length(); next(pos)) { - char c = pattern.charAt(pos.getIndex()); - if (Character.isWhitespace(c)) { - seekNonWs(pattern, pos); - c = pattern.charAt(pos.getIndex()); - if (c != START_FMT && c != END_FE) { - error = true; - continue; - } - } - if ((c == START_FMT || c == END_FE) && result.length() > 0) { - try { - return Integer.parseInt(result.toString()); - } catch (final NumberFormatException ignored) { - // we've already ensured only digits, so unless something - // outlandishly large was specified we should be okay. - } - } - error = !Character.isDigit(c); - result.append(c); - } - if (error) { - throw new IllegalArgumentException( - "Invalid format argument index at position " + start + ": " - + pattern.substring(start, pos.getIndex())); - } - throw new IllegalArgumentException( - "Unterminated format element at position " + start); + private void getQuotedString(final String pattern, final ParsePosition pos) { + appendQuotedString(pattern, pos, null); } /** - * Parse the format component of a format element. - * - * @param pattern string to parse - * @param pos current parse position - * @return Format description String + * {@inheritDoc} */ - private String parseFormatDescription(final String pattern, final ParsePosition pos) { - final int start = pos.getIndex(); - seekNonWs(pattern, pos); - final int text = pos.getIndex(); - int depth = 1; - for (; pos.getIndex() < pattern.length(); next(pos)) { - switch (pattern.charAt(pos.getIndex())) { - case START_FE: - depth++; - break; - case END_FE: - depth--; - if (depth == 0) { - return pattern.substring(text, pos.getIndex()); - } - break; - case QUOTE: - getQuotedString(pattern, pos); - break; - default: - break; - } - } - throw new IllegalArgumentException( - "Unterminated format element at position " + start); + @Override + public int hashCode() { + int result = super.hashCode(); + result = HASH_SEED * result + Objects.hashCode(registry); + result = HASH_SEED * result + Objects.hashCode(toPattern); + return result; } /** @@ -444,6 +370,93 @@ public class ExtendedMessageFormat extends MessageFormat { return sb.toString(); } + /** + * Convenience method to advance parse position by 1 + * + * @param pos ParsePosition + * @return {@code pos} + */ + private ParsePosition next(final ParsePosition pos) { + pos.setIndex(pos.getIndex() + 1); + return pos; + } + + /** + * Parse the format component of a format element. + * + * @param pattern string to parse + * @param pos current parse position + * @return Format description String + */ + private String parseFormatDescription(final String pattern, final ParsePosition pos) { + final int start = pos.getIndex(); + seekNonWs(pattern, pos); + final int text = pos.getIndex(); + int depth = 1; + for (; pos.getIndex() < pattern.length(); next(pos)) { + switch (pattern.charAt(pos.getIndex())) { + case START_FE: + depth++; + break; + case END_FE: + depth--; + if (depth == 0) { + return pattern.substring(text, pos.getIndex()); + } + break; + case QUOTE: + getQuotedString(pattern, pos); + break; + default: + break; + } + } + throw new IllegalArgumentException( + "Unterminated format element at position " + start); + } + + /** + * Read the argument index from the current format element + * + * @param pattern pattern to parse + * @param pos current parse position + * @return argument index + */ + private int readArgumentIndex(final String pattern, final ParsePosition pos) { + final int start = pos.getIndex(); + seekNonWs(pattern, pos); + final StringBuilder result = new StringBuilder(); + boolean error = false; + for (; !error && pos.getIndex() < pattern.length(); next(pos)) { + char c = pattern.charAt(pos.getIndex()); + if (Character.isWhitespace(c)) { + seekNonWs(pattern, pos); + c = pattern.charAt(pos.getIndex()); + if (c != START_FMT && c != END_FE) { + error = true; + continue; + } + } + if ((c == START_FMT || c == END_FE) && result.length() > 0) { + try { + return Integer.parseInt(result.toString()); + } catch (final NumberFormatException ignored) { + // we've already ensured only digits, so unless something + // outlandishly large was specified we should be okay. + } + } + error = !Character.isDigit(c); + result.append(c); + } + if (error) { + throw new IllegalArgumentException( + "Invalid format argument index at position " + start + ": " + + pattern.substring(start, pos.getIndex())); + } + throw new IllegalArgumentException( + "Unterminated format element at position " + start); + } + /** * Consume whitespace from the current parse position. * @@ -460,69 +473,56 @@ public class ExtendedMessageFormat extends MessageFormat { } /** - * Convenience method to advance parse position by 1 + * Throws UnsupportedOperationException - see class Javadoc for details. * - * @param pos ParsePosition - * @return {@code pos} + * @param formatElementIndex format element index + * @param newFormat the new format + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat */ - private ParsePosition next(final ParsePosition pos) { - pos.setIndex(pos.getIndex() + 1); - return pos; + @Override + public void setFormat(final int formatElementIndex, final Format newFormat) { + throw new UnsupportedOperationException(); } /** - * Consume a quoted string, adding it to {@code appendTo} if - * specified. + * Throws UnsupportedOperationException - see class Javadoc for details. * - * @param pattern pattern to parse - * @param pos current parse position - * @param appendTo optional StringBuilder to append - * @return {@code appendTo} + * @param argumentIndex argument index + * @param newFormat the new format + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat */ - private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos, - final StringBuilder appendTo) { - assert pattern.toCharArray()[pos.getIndex()] == QUOTE : - "Quoted string must start with quote character"; - - // handle quote character at the beginning of the string - if (appendTo != null) { - appendTo.append(QUOTE); - } - next(pos); - - final int start = pos.getIndex(); - final char[] c = pattern.toCharArray(); - for (int i = pos.getIndex(); i < pattern.length(); i++) { - if (c[pos.getIndex()] == QUOTE) { - next(pos); - return appendTo == null ? null : appendTo.append(c, start, - pos.getIndex() - start); - } - next(pos); - } - throw new IllegalArgumentException( - "Unterminated quoted string at position " + start); + @Override + public void setFormatByArgumentIndex(final int argumentIndex, final Format newFormat) { + throw new UnsupportedOperationException(); } /** - * Consume quoted string only + * Throws UnsupportedOperationException - see class Javadoc for details. * - * @param pattern pattern to parse - * @param pos current parse position + * @param newFormats new formats + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat */ - private void getQuotedString(final String pattern, final ParsePosition pos) { - appendQuotedString(pattern, pos, null); + @Override + public void setFormats(final Format[] newFormats) { + throw new UnsupportedOperationException(); } /** - * Learn whether the specified Collection contains non-null elements. - * @param coll to check - * @return {@code true} if some Object was found, {@code false} otherwise. + * Throws UnsupportedOperationException - see class Javadoc for details. + * + * @param newFormats new formats + * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat */ - private boolean containsElements(final Collection coll) { - if (coll == null || coll.isEmpty()) { - return false; - } - return coll.stream().anyMatch(Objects::nonNull); + @Override + public void setFormatsByArgumentIndex(final Format[] newFormats) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toPattern() { + return toPattern; } } diff --git a/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java b/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java index 1d29272cc..6c20d6b61 100644 --- a/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java +++ b/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java @@ -44,28 +44,6 @@ public class FormattableUtils { */ private static final String SIMPLEST_FORMAT = "%s"; - /** - * {@link FormattableUtils} instances should NOT be constructed in - * standard programming. Instead, the methods of the class should be invoked - * statically. - * - *

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

- */ - public FormattableUtils() { - } - - /** - * Gets the default formatted representation of the specified - * {@link Formattable}. - * - * @param formattable the instance to convert to a string, not null - * @return the resulting string, not null - */ - public static String toString(final Formattable formattable) { - return String.format(SIMPLEST_FORMAT, formattable); - } - /** * Handles the common {@link Formattable} operations of truncate-pad-append, * with no ellipsis on precision overflow, and padding width underflow with @@ -100,24 +78,6 @@ public class FormattableUtils { return append(seq, formatter, flags, width, precision, padChar, null); } - /** - * Handles the common {@link Formattable} operations of truncate-pad-append, - * padding width underflow with spaces. - * - * @param seq the string to handle, not null - * @param formatter the destination formatter, not null - * @param flags the flags for formatting, see {@link Formattable} - * @param width the width of the output, see {@link Formattable} - * @param precision the precision of the output, see {@link Formattable} - * @param ellipsis the ellipsis to use when precision dictates truncation, null or - * empty causes a hard truncation - * @return the {@code formatter} instance, not null - */ - public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, - final int precision, final CharSequence ellipsis) { - return append(seq, formatter, flags, width, precision, ' ', ellipsis); - } - /** * Handles the common {@link Formattable} operations of truncate-pad-append. * @@ -148,4 +108,44 @@ public class FormattableUtils { return formatter; } + /** + * Handles the common {@link Formattable} operations of truncate-pad-append, + * padding width underflow with spaces. + * + * @param seq the string to handle, not null + * @param formatter the destination formatter, not null + * @param flags the flags for formatting, see {@link Formattable} + * @param width the width of the output, see {@link Formattable} + * @param precision the precision of the output, see {@link Formattable} + * @param ellipsis the ellipsis to use when precision dictates truncation, null or + * empty causes a hard truncation + * @return the {@code formatter} instance, not null + */ + public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, + final int precision, final CharSequence ellipsis) { + return append(seq, formatter, flags, width, precision, ' ', ellipsis); + } + + /** + * Gets the default formatted representation of the specified + * {@link Formattable}. + * + * @param formattable the instance to convert to a string, not null + * @return the resulting string, not null + */ + public static String toString(final Formattable formattable) { + return String.format(SIMPLEST_FORMAT, formattable); + } + + /** + * {@link FormattableUtils} instances should NOT be constructed in + * standard programming. Instead, the methods of the class should be invoked + * statically. + * + *

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

+ */ + public FormattableUtils() { + } + } diff --git a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java index c5c22e089..671ccf77e 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java +++ b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java @@ -83,24 +83,198 @@ import org.apache.commons.lang3.builder.Builder; @Deprecated public class StrBuilder implements CharSequence, Appendable, Serializable, Builder { + /** + * Inner class to allow StrBuilder to operate as a reader. + */ + class StrBuilderReader extends Reader { + /** The current stream position. */ + private int pos; + /** The last mark position. */ + private int mark; + + /** + * Default constructor. + */ + StrBuilderReader() { + } + + /** {@inheritDoc} */ + @Override + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public void mark(final int readAheadLimit) { + mark = pos; + } + + /** {@inheritDoc} */ + @Override + public boolean markSupported() { + return true; + } + + /** {@inheritDoc} */ + @Override + public int read() { + if (!ready()) { + return -1; + } + return StrBuilder.this.charAt(pos++); + } + + /** {@inheritDoc} */ + @Override + public int read(final char[] b, final int off, int len) { + if (off < 0 || len < 0 || off > b.length || + off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + if (pos >= StrBuilder.this.size()) { + return -1; + } + if (pos + len > size()) { + len = StrBuilder.this.size() - pos; + } + StrBuilder.this.getChars(pos, pos + len, b, off); + pos += len; + return len; + } + + /** {@inheritDoc} */ + @Override + public boolean ready() { + return pos < StrBuilder.this.size(); + } + + /** {@inheritDoc} */ + @Override + public void reset() { + pos = mark; + } + + /** {@inheritDoc} */ + @Override + public long skip(long n) { + if (pos + n > StrBuilder.this.size()) { + n = StrBuilder.this.size() - pos; + } + if (n < 0) { + return 0; + } + pos = Math.addExact(pos, Math.toIntExact(n)); + return n; + } + } + + /** + * Inner class to allow StrBuilder to operate as a tokenizer. + */ + class StrBuilderTokenizer extends StrTokenizer { + + /** + * Default constructor. + */ + StrBuilderTokenizer() { + } + + /** {@inheritDoc} */ + @Override + public String getContent() { + final String str = super.getContent(); + if (str == null) { + return StrBuilder.this.toString(); + } + return str; + } + + /** {@inheritDoc} */ + @Override + protected List tokenize(final char[] chars, final int offset, final int count) { + if (chars == null) { + return super.tokenize(StrBuilder.this.buffer, 0, StrBuilder.this.size()); + } + return super.tokenize(chars, offset, count); + } + } + + /** + * Inner class to allow StrBuilder to operate as a writer. + */ + class StrBuilderWriter extends Writer { + + /** + * Default constructor. + */ + StrBuilderWriter() { + } + + /** {@inheritDoc} */ + @Override + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public void flush() { + // do nothing + } + + /** {@inheritDoc} */ + @Override + public void write(final char[] cbuf) { + StrBuilder.this.append(cbuf); + } + + /** {@inheritDoc} */ + @Override + public void write(final char[] cbuf, final int off, final int len) { + StrBuilder.this.append(cbuf, off, len); + } + + /** {@inheritDoc} */ + @Override + public void write(final int c) { + StrBuilder.this.append((char) c); + } + + /** {@inheritDoc} */ + @Override + public void write(final String str) { + StrBuilder.this.append(str); + } + + /** {@inheritDoc} */ + @Override + public void write(final String str, final int off, final int len) { + StrBuilder.this.append(str, off, len); + } + } /** * The extra capacity for new builders. */ static final int CAPACITY = 32; - /** * Required for serialization support. * * @see java.io.Serializable */ private static final long serialVersionUID = 7628716375283629643L; - /** Internal data storage. */ protected char[] buffer; // TODO make private? + /** Current size of the buffer. */ protected int size; // TODO make private? + /** The new line. */ private String newLine; + /** The null text. */ private String nullText; @@ -139,501 +313,92 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Gets the text to be appended when a new line is added. + * Appends a boolean value to the string builder. * - * @return the new line text, null means use system default - */ - public String getNewLineText() { - return newLine; - } - - /** - * Sets the text to be appended when a new line is added. - * - * @param newLine the new line text, null means use system default + * @param value the value to append * @return this, to enable chaining */ - public StrBuilder setNewLineText(final String newLine) { - this.newLine = newLine; - return this; - } - - /** - * Gets the text to be appended when null is added. - * - * @return the null text, null means no append - */ - public String getNullText() { - return nullText; - } - - /** - * Sets the text to be appended when null is added. - * - * @param nullText the null text, null means no append - * @return this, to enable chaining - */ - public StrBuilder setNullText(String nullText) { - if (StringUtils.isEmpty(nullText)) { - nullText = null; - } - this.nullText = nullText; - return this; - } - - /** - * Gets the length of the string builder. - * - * @return the length - */ - @Override - public int length() { - return size; - } - - /** - * Updates the length of the builder by either dropping the last characters - * or adding filler of Unicode zero. - * - * @param length the length to set to, must be zero or positive - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the length is negative - */ - public StrBuilder setLength(final int length) { - if (length < 0) { - throw new StringIndexOutOfBoundsException(length); - } - if (length < size) { - size = length; - } else if (length > size) { - ensureCapacity(length); - final int oldEnd = size; - size = length; - for (int i = oldEnd; i < length; i++) { - buffer[i] = CharUtils.NUL; - } - } - return this; - } - - /** - * Gets the current size of the internal character array buffer. - * - * @return the capacity - */ - public int capacity() { - return buffer.length; - } - - /** - * Checks the capacity and ensures that it is at least the size specified. - * - * @param capacity the capacity to ensure - * @return this, to enable chaining - */ - public StrBuilder ensureCapacity(final int capacity) { - if (capacity > buffer.length) { - final char[] old = buffer; - buffer = new char[capacity * 2]; - System.arraycopy(old, 0, buffer, 0, size); - } - return this; - } - - /** - * Minimizes the capacity to the actual length of the string. - * - * @return this, to enable chaining - */ - public StrBuilder minimizeCapacity() { - if (buffer.length > length()) { - final char[] old = buffer; - buffer = new char[length()]; - System.arraycopy(old, 0, buffer, 0, size); - } - return this; - } - - /** - * Gets the length of the string builder. - *

- * This method is the same as {@link #length()} and is provided to match the - * API of Collections. - *

- * - * @return the length - */ - public int size() { - return size; - } - - /** - * Checks is the string builder is empty (convenience Collections API style method). - *

- * This method is the same as checking {@link #length()} and is provided to match the - * API of Collections. - *

- * - * @return {@code true} if the size is {@code 0}. - */ - public boolean isEmpty() { - return size == 0; - } - - /** - * Checks is the string builder is not empty (convenience Collections API style method). - *

- * This method is the same as checking {@link #length()} and is provided to match the - * API of Collections. - *

- * - * @return {@code true} if the size is greater than {@code 0}. - * @since 3.12.0 - */ - public boolean isNotEmpty() { - return size > 0; - } - - /** - * Clears the string builder (convenience Collections API style method). - *

- * This method does not reduce the size of the internal character buffer. - * To do that, call {@code clear()} followed by {@link #minimizeCapacity()}. - *

- *

- * This method is the same as {@link #setLength(int)} called with zero - * and is provided to match the API of Collections. - *

- * - * @return this, to enable chaining - */ - public StrBuilder clear() { - size = 0; - return this; - } - - /** - * Gets the character at the specified index. - * - * @see #setCharAt(int, char) - * @see #deleteCharAt(int) - * @param index the index to retrieve, must be valid - * @return the character at the index - * @throws IndexOutOfBoundsException if the index is invalid - */ - @Override - public char charAt(final int index) { - if (index < 0 || index >= length()) { - throw new StringIndexOutOfBoundsException(index); - } - return buffer[index]; - } - - /** - * Sets the character at the specified index. - * - * @see #charAt(int) - * @see #deleteCharAt(int) - * @param index the index to set - * @param ch the new character - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder setCharAt(final int index, final char ch) { - if (index < 0 || index >= length()) { - throw new StringIndexOutOfBoundsException(index); - } - buffer[index] = ch; - return this; - } - - /** - * Deletes the character at the specified index. - * - * @see #charAt(int) - * @see #setCharAt(int, char) - * @param index the index to delete - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder deleteCharAt(final int index) { - if (index < 0 || index >= size) { - throw new StringIndexOutOfBoundsException(index); - } - deleteImpl(index, index + 1, 1); - return this; - } - - /** - * Copies the builder's character array into a new character array. - * - * @return a new array that represents the contents of the builder - */ - public char[] toCharArray() { - if (size == 0) { - return ArrayUtils.EMPTY_CHAR_ARRAY; - } - final char[] chars = new char[size]; - System.arraycopy(buffer, 0, chars, 0, size); - return chars; - } - - /** - * Copies part of the builder's character array into a new character array. - * - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except that - * if too large it is treated as end of string - * @return a new array that holds part of the contents of the builder - * @throws IndexOutOfBoundsException if startIndex is invalid, - * or if endIndex is invalid (but endIndex greater than size is valid) - */ - public char[] toCharArray(final int startIndex, int endIndex) { - endIndex = validateRange(startIndex, endIndex); - final int len = endIndex - startIndex; - if (len == 0) { - return ArrayUtils.EMPTY_CHAR_ARRAY; - } - final char[] chars = new char[len]; - System.arraycopy(buffer, startIndex, chars, 0, len); - return chars; - } - - /** - * Copies the character array into the specified array. - * - * @param destination the destination array, null will cause an array to be created - * @return the input array, unless that was null or too small - */ - public char[] getChars(char[] destination) { - final int len = length(); - if (destination == null || destination.length < len) { - destination = new char[len]; - } - System.arraycopy(buffer, 0, destination, 0, len); - return destination; - } - - /** - * Copies the character array into the specified array. - * - * @param startIndex first index to copy, inclusive, must be valid - * @param endIndex last index, exclusive, must be valid - * @param destination the destination array, must not be null or too small - * @param destinationIndex the index to start copying in destination - * @throws NullPointerException if the array is null - * @throws IndexOutOfBoundsException if any index is invalid - */ - public void getChars(final int startIndex, final int endIndex, final char[] destination, final int destinationIndex) { - if (startIndex < 0) { - throw new StringIndexOutOfBoundsException(startIndex); - } - if (endIndex < 0 || endIndex > length()) { - throw new StringIndexOutOfBoundsException(endIndex); - } - if (startIndex > endIndex) { - throw new StringIndexOutOfBoundsException("end < start"); - } - System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); - } - - /** - * If possible, reads chars from the provided {@link Readable} directly into underlying - * character buffer without making extra copies. - * - * @param readable object to read from - * @return the number of characters read - * @throws IOException if an I/O error occurs. - * - * @since 3.4 - * @see #appendTo(Appendable) - */ - public int readFrom(final Readable readable) throws IOException { - final int oldSize = size; - if (readable instanceof Reader) { - final Reader r = (Reader) readable; - ensureCapacity(size + 1); - int read; - while ((read = r.read(buffer, size, buffer.length - size)) != -1) { - size += read; - ensureCapacity(size + 1); - } - } else if (readable instanceof CharBuffer) { - final CharBuffer cb = (CharBuffer) readable; - final int remaining = cb.remaining(); - ensureCapacity(size + remaining); - cb.get(buffer, size, remaining); - size += remaining; + public StrBuilder append(final boolean value) { + if (value) { + ensureCapacity(size + 4); + buffer[size++] = 't'; + buffer[size++] = 'r'; + buffer[size++] = 'u'; } else { - while (true) { - ensureCapacity(size + 1); - final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size); - final int read = readable.read(buf); - if (read == -1) { - break; - } - size += read; - } + ensureCapacity(size + 5); + buffer[size++] = 'f'; + buffer[size++] = 'a'; + buffer[size++] = 'l'; + buffer[size++] = 's'; } - return size - oldSize; + buffer[size++] = 'e'; + return this; } /** - * Appends the new line string to this string builder. - *

- * The new line string can be altered using {@link #setNewLineText(String)}. - * This might be used to force the output to always use Unix line endings - * even when on Windows. - *

+ * Appends a char value to the string builder. * - * @return this, to enable chaining - */ - public StrBuilder appendNewLine() { - if (newLine == null) { - append(System.lineSeparator()); - return this; - } - return append(newLine); - } - - /** - * Appends the text representing {@code null} to this string builder. - * - * @return this, to enable chaining - */ - public StrBuilder appendNull() { - if (nullText == null) { - return this; - } - return append(nullText); - } - - /** - * Appends an object to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param obj the object to append - * @return this, to enable chaining - */ - public StrBuilder append(final Object obj) { - if (obj == null) { - return appendNull(); - } - if (obj instanceof CharSequence) { - return append((CharSequence) obj); - } - return append(obj.toString()); - } - - /** - * Appends a CharSequence to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param seq the CharSequence to append + * @param ch the value to append * @return this, to enable chaining * @since 3.0 */ @Override - public StrBuilder append(final CharSequence seq) { - if (seq == null) { - return appendNull(); - } - if (seq instanceof StrBuilder) { - return append((StrBuilder) seq); - } - if (seq instanceof StringBuilder) { - return append((StringBuilder) seq); - } - if (seq instanceof StringBuffer) { - return append((StringBuffer) seq); - } - if (seq instanceof CharBuffer) { - return append((CharBuffer) seq); - } - return append(seq.toString()); + public StrBuilder append(final char ch) { + final int len = length(); + ensureCapacity(len + 1); + buffer[size++] = ch; + return this; } /** - * Appends part of a CharSequence to this string builder. + * Appends a char array to the string builder. * Appending null will call {@link #appendNull()}. * - * @param seq the CharSequence to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid - * @return this, to enable chaining - * @since 3.0 - */ - @Override - public StrBuilder append(final CharSequence seq, final int startIndex, final int length) { - if (seq == null) { - return appendNull(); - } - return append(seq.toString(), startIndex, length); - } - - /** - * Appends a string to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param str the string to append + * @param chars the char array to append * @return this, to enable chaining */ - public StrBuilder append(final String str) { - if (str == null) { + public StrBuilder append(final char[] chars) { + if (chars == null) { return appendNull(); } - final int strLen = str.length(); + final int strLen = chars.length; if (strLen > 0) { final int len = length(); ensureCapacity(len + strLen); - str.getChars(0, strLen, buffer, len); + System.arraycopy(chars, 0, buffer, len, strLen); size += strLen; } return this; } - /** - * Appends part of a string to this string builder. + * Appends a char array to the string builder. * Appending null will call {@link #appendNull()}. * - * @param str the string to append + * @param chars the char array to append * @param startIndex the start index, inclusive, must be valid * @param length the length to append, must be valid * @return this, to enable chaining */ - public StrBuilder append(final String str, final int startIndex, final int length) { - if (str == null) { + public StrBuilder append(final char[] chars, final int startIndex, final int length) { + if (chars == null) { return appendNull(); } - if (startIndex < 0 || startIndex > str.length()) { - throw new StringIndexOutOfBoundsException("startIndex must be valid"); + if (startIndex < 0 || startIndex > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid startIndex: " + length); } - if (length < 0 || startIndex + length > str.length()) { - throw new StringIndexOutOfBoundsException("length must be valid"); + if (length < 0 || startIndex + length > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); } if (length > 0) { final int len = length(); ensureCapacity(len + length); - str.getChars(startIndex, startIndex + length, buffer, len); + System.arraycopy(chars, startIndex, buffer, len, length); size += length; } return this; } - /** - * Calls {@link String#format(String, Object...)} and appends the result. - * - * @param format the format string - * @param objs the objects to use in the format string - * @return {@code this} to enable chaining - * @see String#format(String, Object...) - * @since 3.2 - */ - public StrBuilder append(final String format, final Object... objs) { - return append(String.format(format, objs)); - } - /** * Appends the contents of a char buffer to this string builder. * Appending null will call {@link #appendNull()}. @@ -690,6 +455,220 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build return this; } + /** + * Appends a CharSequence to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param seq the CharSequence to append + * @return this, to enable chaining + * @since 3.0 + */ + @Override + public StrBuilder append(final CharSequence seq) { + if (seq == null) { + return appendNull(); + } + if (seq instanceof StrBuilder) { + return append((StrBuilder) seq); + } + if (seq instanceof StringBuilder) { + return append((StringBuilder) seq); + } + if (seq instanceof StringBuffer) { + return append((StringBuffer) seq); + } + if (seq instanceof CharBuffer) { + return append((CharBuffer) seq); + } + return append(seq.toString()); + } + + /** + * Appends part of a CharSequence to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param seq the CharSequence to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 3.0 + */ + @Override + public StrBuilder append(final CharSequence seq, final int startIndex, final int length) { + if (seq == null) { + return appendNull(); + } + return append(seq.toString(), startIndex, length); + } + + /** + * Appends a double value to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final double value) { + return append(String.valueOf(value)); + } + + /** + * Appends a float value to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final float value) { + return append(String.valueOf(value)); + } + + /** + * Appends an int value to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final int value) { + return append(String.valueOf(value)); + } + + /** + * Appends a long value to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final long value) { + return append(String.valueOf(value)); + } + + /** + * Appends an object to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param obj the object to append + * @return this, to enable chaining + */ + public StrBuilder append(final Object obj) { + if (obj == null) { + return appendNull(); + } + if (obj instanceof CharSequence) { + return append((CharSequence) obj); + } + return append(obj.toString()); + } + + /** + * Appends another string builder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @return this, to enable chaining + */ + public StrBuilder append(final StrBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(str.buffer, 0, buffer, len, strLen); + size += strLen; + } + return this; + } + + /** + * Appends part of a string builder to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final StrBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || startIndex + length > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends a string to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @return this, to enable chaining + */ + public StrBuilder append(final String str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a string to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + */ + public StrBuilder append(final String str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || startIndex + length > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Calls {@link String#format(String, Object...)} and appends the result. + * + * @param format the format string + * @param objs the objects to use in the format string + * @return {@code this} to enable chaining + * @see String#format(String, Object...) + * @since 3.2 + */ + public StrBuilder append(final String format, final Object... objs) { + return append(String.format(format, objs)); + } + /** * Appends a string buffer to this string builder. * Appending null will call {@link #appendNull()}. @@ -791,179 +770,247 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Appends another string builder to this string builder. - * Appending null will call {@link #appendNull()}. + * Appends each item in an iterable to the builder without any separators. + * Appending a null iterable will have no effect. + * Each object is appended using {@link #append(Object)}. * - * @param str the string builder to append + * @param iterable the iterable to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final StrBuilder str) { - if (str == null) { - return appendNull(); - } - final int strLen = str.length(); - if (strLen > 0) { - final int len = length(); - ensureCapacity(len + strLen); - System.arraycopy(str.buffer, 0, buffer, len, strLen); - size += strLen; + public StrBuilder appendAll(final Iterable iterable) { + if (iterable != null) { + iterable.forEach(this::append); } return this; } /** - * Appends part of a string builder to this string builder. - * Appending null will call {@link #appendNull()}. + * Appends each item in an iterator to the builder without any separators. + * Appending a null iterator will have no effect. + * Each object is appended using {@link #append(Object)}. * - * @param str the string to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid + * @param it the iterator to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final StrBuilder str, final int startIndex, final int length) { - if (str == null) { - return appendNull(); + public StrBuilder appendAll(final Iterator it) { + if (it != null) { + it.forEachRemaining(this::append); } - if (startIndex < 0 || startIndex > str.length()) { - throw new StringIndexOutOfBoundsException("startIndex must be valid"); - } - if (length < 0 || startIndex + length > str.length()) { - throw new StringIndexOutOfBoundsException("length must be valid"); - } - if (length > 0) { - final int len = length(); - ensureCapacity(len + length); - str.getChars(startIndex, startIndex + length, buffer, len); - size += length; + return this; + } + + + /** + * Appends each item in an array to the builder without any separators. + * Appending a null array will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param the element type + * @param array the array to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(@SuppressWarnings("unchecked") final T... array) { + /* + * @SuppressWarnings used to hide warning about vararg usage. We cannot + * use @SafeVarargs, since this method is not final. Using @SuppressWarnings + * is fine, because it isn't inherited by subclasses, so each subclass must + * vouch for itself whether its use of 'array' is safe. + */ + if (ArrayUtils.isNotEmpty(array)) { + for (final Object element : array) { + append(element); + } } return this; } /** - * Appends a char array to the string builder. - * Appending null will call {@link #appendNull()}. + * Appends an object to the builder padding on the left to a fixed width. + * The {@code String.valueOf} of the {@code int} value is used. + * If the formatted value is larger than the length, the left-hand side is lost. * - * @param chars the char array to append + * @param value the value to append + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use * @return this, to enable chaining */ - public StrBuilder append(final char[] chars) { - if (chars == null) { - return appendNull(); - } - final int strLen = chars.length; - if (strLen > 0) { - final int len = length(); - ensureCapacity(len + strLen); - System.arraycopy(chars, 0, buffer, len, strLen); - size += strLen; + public StrBuilder appendFixedWidthPadLeft(final int value, final int width, final char padChar) { + return appendFixedWidthPadLeft(String.valueOf(value), width, padChar); + } + + /** + * Appends an object to the builder padding on the left to a fixed width. + * The {@code toString} of the object is used. + * If the object is larger than the length, the left-hand side is lost. + * If the object is null, the null text value is used. + * + * @param obj the object to append, null uses null text + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadLeft(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = ObjectUtils.toString(obj, this::getNullText); + if (str == null) { + str = StringUtils.EMPTY; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(strLen - width, strLen, buffer, size); + } else { + final int padLen = width - strLen; + for (int i = 0; i < padLen; i++) { + buffer[size + i] = padChar; + } + str.getChars(0, strLen, buffer, size + padLen); + } + size += width; } return this; } /** - * Appends a char array to the string builder. - * Appending null will call {@link #appendNull()}. + * Appends an object to the builder padding on the right to a fixed length. + * The {@code String.valueOf} of the {@code int} value is used. + * If the object is larger than the length, the right-hand side is lost. * - * @param chars the char array to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid + * @param value the value to append + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use * @return this, to enable chaining */ - public StrBuilder append(final char[] chars, final int startIndex, final int length) { - if (chars == null) { - return appendNull(); - } - if (startIndex < 0 || startIndex > chars.length) { - throw new StringIndexOutOfBoundsException("Invalid startIndex: " + length); - } - if (length < 0 || startIndex + length > chars.length) { - throw new StringIndexOutOfBoundsException("Invalid length: " + length); - } - if (length > 0) { - final int len = length(); - ensureCapacity(len + length); - System.arraycopy(chars, startIndex, buffer, len, length); - size += length; + public StrBuilder appendFixedWidthPadRight(final int value, final int width, final char padChar) { + return appendFixedWidthPadRight(String.valueOf(value), width, padChar); + } + + /** + * Appends an object to the builder padding on the right to a fixed length. + * The {@code toString} of the object is used. + * If the object is larger than the length, the right-hand side is lost. + * If the object is null, null text value is used. + * + * @param obj the object to append, null uses null text + * @param width the fixed field width, zero or negative has no effect + * @param padChar the pad character to use + * @return this, to enable chaining + */ + public StrBuilder appendFixedWidthPadRight(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = ObjectUtils.toString(obj, this::getNullText); + if (str == null) { + str = StringUtils.EMPTY; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(0, width, buffer, size); + } else { + final int padLen = width - strLen; + str.getChars(0, strLen, buffer, size); + for (int i = 0; i < padLen; i++) { + buffer[size + strLen + i] = padChar; + } + } + size += width; } return this; } /** - * Appends a boolean value to the string builder. + * Appends a boolean value followed by a new line to the string builder. * * @param value the value to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final boolean value) { - if (value) { - ensureCapacity(size + 4); - buffer[size++] = 't'; - buffer[size++] = 'r'; - buffer[size++] = 'u'; - } else { - ensureCapacity(size + 5); - buffer[size++] = 'f'; - buffer[size++] = 'a'; - buffer[size++] = 'l'; - buffer[size++] = 's'; - } - buffer[size++] = 'e'; - return this; + public StrBuilder appendln(final boolean value) { + return append(value).appendNewLine(); } /** - * Appends a char value to the string builder. + * Appends a char value followed by a new line to the string builder. * * @param ch the value to append * @return this, to enable chaining - * @since 3.0 + * @since 2.3 */ - @Override - public StrBuilder append(final char ch) { - final int len = length(); - ensureCapacity(len + 1); - buffer[size++] = ch; - return this; + public StrBuilder appendln(final char ch) { + return append(ch).appendNewLine(); } /** - * Appends an int value to the string builder using {@code String.valueOf}. + * Appends a char array followed by a new line to the string builder. + * Appending null will call {@link #appendNull()}. * - * @param value the value to append + * @param chars the char array to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final int value) { - return append(String.valueOf(value)); + public StrBuilder appendln(final char[] chars) { + return append(chars).appendNewLine(); } /** - * Appends a long value to the string builder using {@code String.valueOf}. + * Appends a char array followed by a new line to the string builder. + * Appending null will call {@link #appendNull()}. * - * @param value the value to append + * @param chars the char array to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final long value) { - return append(String.valueOf(value)); + public StrBuilder appendln(final char[] chars, final int startIndex, final int length) { + return append(chars, startIndex, length).appendNewLine(); } /** - * Appends a float value to the string builder using {@code String.valueOf}. + * Appends a double value followed by a new line to the string builder using {@code String.valueOf}. * * @param value the value to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final float value) { - return append(String.valueOf(value)); + public StrBuilder appendln(final double value) { + return append(value).appendNewLine(); } /** - * Appends a double value to the string builder using {@code String.valueOf}. + * Appends a float value followed by a new line to the string builder using {@code String.valueOf}. * * @param value the value to append * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder append(final double value) { - return append(String.valueOf(value)); + public StrBuilder appendln(final float value) { + return append(value).appendNewLine(); + } + + /** + * Appends an int value followed by a new line to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final int value) { + return append(value).appendNewLine(); + } + + /** + * Appends a long value followed by a new line to the string builder using {@code String.valueOf}. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final long value) { + return append(value).appendNewLine(); } /** @@ -978,6 +1025,32 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build return append(obj).appendNewLine(); } + /** + * Appends another string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string builder to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StrBuilder str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string builder followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StrBuilder str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + /** * Appends a string followed by a new line to this string builder. * Appending null will call {@link #appendNull()}. @@ -1029,6 +1102,20 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build return append(str).appendNewLine(); } + /** + * Appends part of a string buffer followed by a new line to this string builder. + * Appending null will call {@link #appendNull()}. + * + * @param str the string to append + * @param startIndex the start index, inclusive, must be valid + * @param length the length to append, must be valid + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(final StringBuffer str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + /** * Appends a string builder followed by a new line to this string builder. * Appending null will call {@link #appendNull()}. @@ -1056,322 +1143,48 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Appends part of a string buffer followed by a new line to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param str the string to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final StringBuffer str, final int startIndex, final int length) { - return append(str, startIndex, length).appendNewLine(); - } - - /** - * Appends another string builder followed by a new line to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param str the string builder to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final StrBuilder str) { - return append(str).appendNewLine(); - } - - /** - * Appends part of a string builder followed by a new line to this string builder. - * Appending null will call {@link #appendNull()}. - * - * @param str the string to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final StrBuilder str, final int startIndex, final int length) { - return append(str, startIndex, length).appendNewLine(); - } - - /** - * Appends a char array followed by a new line to the string builder. - * Appending null will call {@link #appendNull()}. - * - * @param chars the char array to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final char[] chars) { - return append(chars).appendNewLine(); - } - - /** - * Appends a char array followed by a new line to the string builder. - * Appending null will call {@link #appendNull()}. - * - * @param chars the char array to append - * @param startIndex the start index, inclusive, must be valid - * @param length the length to append, must be valid - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final char[] chars, final int startIndex, final int length) { - return append(chars, startIndex, length).appendNewLine(); - } - - /** - * Appends a boolean value followed by a new line to the string builder. - * - * @param value the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final boolean value) { - return append(value).appendNewLine(); - } - - /** - * Appends a char value followed by a new line to the string builder. - * - * @param ch the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final char ch) { - return append(ch).appendNewLine(); - } - - /** - * Appends an int value followed by a new line to the string builder using {@code String.valueOf}. - * - * @param value the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final int value) { - return append(value).appendNewLine(); - } - - /** - * Appends a long value followed by a new line to the string builder using {@code String.valueOf}. - * - * @param value the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final long value) { - return append(value).appendNewLine(); - } - - /** - * Appends a float value followed by a new line to the string builder using {@code String.valueOf}. - * - * @param value the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final float value) { - return append(value).appendNewLine(); - } - - /** - * Appends a double value followed by a new line to the string builder using {@code String.valueOf}. - * - * @param value the value to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendln(final double value) { - return append(value).appendNewLine(); - } - - /** - * Appends each item in an array to the builder without any separators. - * Appending a null array will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param the element type - * @param array the array to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendAll(@SuppressWarnings("unchecked") final T... array) { - /* - * @SuppressWarnings used to hide warning about vararg usage. We cannot - * use @SafeVarargs, since this method is not final. Using @SuppressWarnings - * is fine, because it isn't inherited by subclasses, so each subclass must - * vouch for itself whether its use of 'array' is safe. - */ - if (ArrayUtils.isNotEmpty(array)) { - for (final Object element : array) { - append(element); - } - } - return this; - } - - /** - * Appends each item in an iterable to the builder without any separators. - * Appending a null iterable will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param iterable the iterable to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendAll(final Iterable iterable) { - if (iterable != null) { - iterable.forEach(this::append); - } - return this; - } - - /** - * Appends each item in an iterator to the builder without any separators. - * Appending a null iterator will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param it the iterator to append - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendAll(final Iterator it) { - if (it != null) { - it.forEachRemaining(this::append); - } - return this; - } - - /** - * Appends an array placing separators between each value, but - * not before the first or after the last. - * Appending a null array will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param array the array to append - * @param separator the separator to use, null means no separator - * @return this, to enable chaining - */ - public StrBuilder appendWithSeparators(final Object[] array, final String separator) { - if (array != null && array.length > 0) { - final String sep = Objects.toString(separator, ""); - append(array[0]); - for (int i = 1; i < array.length; i++) { - append(sep); - append(array[i]); - } - } - return this; - } - - /** - * Appends an iterable placing separators between each value, but - * not before the first or after the last. - * Appending a null iterable will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param iterable the iterable to append - * @param separator the separator to use, null means no separator - * @return this, to enable chaining - */ - public StrBuilder appendWithSeparators(final Iterable iterable, final String separator) { - if (iterable != null) { - final String sep = Objects.toString(separator, ""); - final Iterator it = iterable.iterator(); - while (it.hasNext()) { - append(it.next()); - if (it.hasNext()) { - append(sep); - } - } - } - return this; - } - - /** - * Appends an iterator placing separators between each value, but - * not before the first or after the last. - * Appending a null iterator will have no effect. - * Each object is appended using {@link #append(Object)}. - * - * @param it the iterator to append - * @param separator the separator to use, null means no separator - * @return this, to enable chaining - */ - public StrBuilder appendWithSeparators(final Iterator it, final String separator) { - if (it != null) { - final String sep = Objects.toString(separator, ""); - while (it.hasNext()) { - append(it.next()); - if (it.hasNext()) { - append(sep); - } - } - } - return this; - } - - /** - * Appends a separator if the builder is currently non-empty. - * Appending a null separator will have no effect. - * The separator is appended using {@link #append(String)}. + * Appends the new line string to this string builder. *

- * This method is useful for adding a separator each time around the - * loop except the first. - *

- *
-     * for (Iterator it = list.iterator(); it.hasNext(); ) {
-     *   appendSeparator(",");
-     *   append(it.next());
-     * }
-     * 
- *

- * Note that for this simple example, you should use - * {@link #appendWithSeparators(Iterable, String)}. + * The new line string can be altered using {@link #setNewLineText(String)}. + * This might be used to force the output to always use Unix line endings + * even when on Windows. *

* - * @param separator the separator to use, null means no separator * @return this, to enable chaining - * @since 2.3 */ - public StrBuilder appendSeparator(final String separator) { - return appendSeparator(separator, null); + public StrBuilder appendNewLine() { + if (newLine == null) { + append(System.lineSeparator()); + return this; + } + return append(newLine); } /** - * Appends one of both separators to the StrBuilder. - * If the builder is currently empty it will append the defaultIfEmpty-separator - * Otherwise it will append the standard-separator + * Appends the text representing {@code null} to this string builder. * - * Appending a null separator will have no effect. - * The separator is appended using {@link #append(String)}. - *

- * This method is for example useful for constructing queries - *

- *
-     * StrBuilder whereClause = new StrBuilder();
-     * if (searchCommand.getPriority() != null) {
-     *  whereClause.appendSeparator(" and", " where");
-     *  whereClause.append(" priority = ?")
-     * }
-     * if (searchCommand.getComponent() != null) {
-     *  whereClause.appendSeparator(" and", " where");
-     *  whereClause.append(" component = ?")
-     * }
-     * selectClause.append(whereClause)
-     * 
- * - * @param standard the separator if builder is not empty, null means no separator - * @param defaultIfEmpty the separator if builder is empty, null means no separator * @return this, to enable chaining - * @since 2.5 */ - public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) { - final String str = isEmpty() ? defaultIfEmpty : standard; - if (str != null) { - append(str); + public StrBuilder appendNull() { + if (nullText == null) { + return this; + } + return append(nullText); + } + + /** + * Appends the pad character to the builder the specified number of times. + * + * @param length the length to append, negative means no append + * @param padChar the character to append + * @return this, to enable chaining + */ + public StrBuilder appendPadding(final int length, final char padChar) { + if (length >= 0) { + ensureCapacity(size + length); + for (int i = 0; i < length; i++) { + buffer[size++] = padChar; + } } return this; } @@ -1422,34 +1235,6 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } return this; } - /** - * Appends a separator to the builder if the loop index is greater than zero. - * Appending a null separator will have no effect. - * The separator is appended using {@link #append(String)}. - *

- * This method is useful for adding a separator each time around the - * loop except the first. - *

- *
-     * for (int i = 0; i < list.size(); i++) {
-     *   appendSeparator(",", i);
-     *   append(list.get(i));
-     * }
-     * 
- * Note that for this simple example, you should use - * {@link #appendWithSeparators(Iterable, String)}. - * - * @param separator the separator to use, null means no separator - * @param loopIndex the loop index - * @return this, to enable chaining - * @since 2.3 - */ - public StrBuilder appendSeparator(final String separator, final int loopIndex) { - if (separator != null && loopIndex > 0) { - append(separator); - } - return this; - } /** * Appends a separator to the builder if the loop index is greater than zero. @@ -1480,498 +1265,142 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Appends the pad character to the builder the specified number of times. + * Appends a separator if the builder is currently non-empty. + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+ *
+     * for (Iterator it = list.iterator(); it.hasNext(); ) {
+     *   appendSeparator(",");
+     *   append(it.next());
+     * }
+     * 
+ *

+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + *

* - * @param length the length to append, negative means no append - * @param padChar the character to append + * @param separator the separator to use, null means no separator * @return this, to enable chaining + * @since 2.3 */ - public StrBuilder appendPadding(final int length, final char padChar) { - if (length >= 0) { - ensureCapacity(size + length); - for (int i = 0; i < length; i++) { - buffer[size++] = padChar; - } + public StrBuilder appendSeparator(final String separator) { + return appendSeparator(separator, null); + } + + /** + * Appends a separator to the builder if the loop index is greater than zero. + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

+ * This method is useful for adding a separator each time around the + * loop except the first. + *

+ *
+     * for (int i = 0; i < list.size(); i++) {
+     *   appendSeparator(",", i);
+     *   append(list.get(i));
+     * }
+     * 
+ * Note that for this simple example, you should use + * {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(final String separator, final int loopIndex) { + if (separator != null && loopIndex > 0) { + append(separator); } return this; } /** - * Appends an object to the builder padding on the left to a fixed width. - * The {@code toString} of the object is used. - * If the object is larger than the length, the left-hand side is lost. - * If the object is null, the null text value is used. + * Appends one of both separators to the StrBuilder. + * If the builder is currently empty it will append the defaultIfEmpty-separator + * Otherwise it will append the standard-separator * - * @param obj the object to append, null uses null text - * @param width the fixed field width, zero or negative has no effect - * @param padChar the pad character to use - * @return this, to enable chaining - */ - public StrBuilder appendFixedWidthPadLeft(final Object obj, final int width, final char padChar) { - if (width > 0) { - ensureCapacity(size + width); - String str = ObjectUtils.toString(obj, this::getNullText); - if (str == null) { - str = StringUtils.EMPTY; - } - final int strLen = str.length(); - if (strLen >= width) { - str.getChars(strLen - width, strLen, buffer, size); - } else { - final int padLen = width - strLen; - for (int i = 0; i < padLen; i++) { - buffer[size + i] = padChar; - } - str.getChars(0, strLen, buffer, size + padLen); - } - size += width; - } - return this; - } - - /** - * Appends an object to the builder padding on the left to a fixed width. - * The {@code String.valueOf} of the {@code int} value is used. - * If the formatted value is larger than the length, the left-hand side is lost. + * Appending a null separator will have no effect. + * The separator is appended using {@link #append(String)}. + *

+ * This method is for example useful for constructing queries + *

+ *
+     * StrBuilder whereClause = new StrBuilder();
+     * if (searchCommand.getPriority() != null) {
+     *  whereClause.appendSeparator(" and", " where");
+     *  whereClause.append(" priority = ?")
+     * }
+     * if (searchCommand.getComponent() != null) {
+     *  whereClause.appendSeparator(" and", " where");
+     *  whereClause.append(" component = ?")
+     * }
+     * selectClause.append(whereClause)
+     * 
* - * @param value the value to append - * @param width the fixed field width, zero or negative has no effect - * @param padChar the pad character to use + * @param standard the separator if builder is not empty, null means no separator + * @param defaultIfEmpty the separator if builder is empty, null means no separator * @return this, to enable chaining + * @since 2.5 */ - public StrBuilder appendFixedWidthPadLeft(final int value, final int width, final char padChar) { - return appendFixedWidthPadLeft(String.valueOf(value), width, padChar); - } - - /** - * Appends an object to the builder padding on the right to a fixed length. - * The {@code toString} of the object is used. - * If the object is larger than the length, the right-hand side is lost. - * If the object is null, null text value is used. - * - * @param obj the object to append, null uses null text - * @param width the fixed field width, zero or negative has no effect - * @param padChar the pad character to use - * @return this, to enable chaining - */ - public StrBuilder appendFixedWidthPadRight(final Object obj, final int width, final char padChar) { - if (width > 0) { - ensureCapacity(size + width); - String str = ObjectUtils.toString(obj, this::getNullText); - if (str == null) { - str = StringUtils.EMPTY; - } - final int strLen = str.length(); - if (strLen >= width) { - str.getChars(0, width, buffer, size); - } else { - final int padLen = width - strLen; - str.getChars(0, strLen, buffer, size); - for (int i = 0; i < padLen; i++) { - buffer[size + strLen + i] = padChar; - } - } - size += width; - } - return this; - } - - /** - * Appends an object to the builder padding on the right to a fixed length. - * The {@code String.valueOf} of the {@code int} value is used. - * If the object is larger than the length, the right-hand side is lost. - * - * @param value the value to append - * @param width the fixed field width, zero or negative has no effect - * @param padChar the pad character to use - * @return this, to enable chaining - */ - public StrBuilder appendFixedWidthPadRight(final int value, final int width, final char padChar) { - return appendFixedWidthPadRight(String.valueOf(value), width, padChar); - } - - /** - * Inserts the string representation of an object into this builder. - * Inserting null will use the stored null text value. - * - * @param index the index to add at, must be valid - * @param obj the object to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final Object obj) { - if (obj == null) { - return insert(index, nullText); - } - return insert(index, obj.toString()); - } - - /** - * Inserts the string into this builder. - * Inserting null will use the stored null text value. - * - * @param index the index to add at, must be valid - * @param str the string to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, String str) { - validateIndex(index); - if (str == null) { - str = nullText; - } + public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) { + final String str = isEmpty() ? defaultIfEmpty : standard; if (str != null) { - final int strLen = str.length(); - if (strLen > 0) { - final int newSize = size + strLen; - ensureCapacity(newSize); - System.arraycopy(buffer, index, buffer, index + strLen, size - index); - size = newSize; - str.getChars(0, strLen, buffer, index); - } + append(str); } return this; } /** - * Inserts the character array into this builder. - * Inserting null will use the stored null text value. + * Appends current contents of this {@link StrBuilder} to the + * provided {@link Appendable}. + *

+ * This method tries to avoid doing any extra copies of contents. + *

* - * @param index the index to add at, must be valid - * @param chars the char array to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final char[] chars) { - validateIndex(index); - if (chars == null) { - return insert(index, nullText); - } - final int len = chars.length; - if (len > 0) { - ensureCapacity(size + len); - System.arraycopy(buffer, index, buffer, index + len, size - index); - System.arraycopy(chars, 0, buffer, index, len); - size += len; - } - return this; - } - - /** - * Inserts part of the character array into this builder. - * Inserting null will use the stored null text value. + * @param appendable the appendable to append data to + * @throws IOException if an I/O error occurs * - * @param index the index to add at, must be valid - * @param chars the char array to insert - * @param offset the offset into the character array to start at, must be valid - * @param length the length of the character array part to copy, must be positive - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if any index is invalid + * @since 3.4 + * @see #readFrom(Readable) */ - public StrBuilder insert(final int index, final char[] chars, final int offset, final int length) { - validateIndex(index); - if (chars == null) { - return insert(index, nullText); - } - if (offset < 0 || offset > chars.length) { - throw new StringIndexOutOfBoundsException("Invalid offset: " + offset); - } - if (length < 0 || offset + length > chars.length) { - throw new StringIndexOutOfBoundsException("Invalid length: " + length); - } - if (length > 0) { - ensureCapacity(size + length); - System.arraycopy(buffer, index, buffer, index + length, size - index); - System.arraycopy(chars, offset, buffer, index, length); - size += length; - } - return this; - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(int index, final boolean value) { - validateIndex(index); - if (value) { - ensureCapacity(size + 4); - System.arraycopy(buffer, index, buffer, index + 4, size - index); - buffer[index++] = 't'; - buffer[index++] = 'r'; - buffer[index++] = 'u'; - buffer[index] = 'e'; - size += 4; + public void appendTo(final Appendable appendable) throws IOException { + if (appendable instanceof Writer) { + ((Writer) appendable).write(buffer, 0, size); + } else if (appendable instanceof StringBuilder) { + ((StringBuilder) appendable).append(buffer, 0, size); + } else if (appendable instanceof StringBuffer) { + ((StringBuffer) appendable).append(buffer, 0, size); + } else if (appendable instanceof CharBuffer) { + ((CharBuffer) appendable).put(buffer, 0, size); } else { - ensureCapacity(size + 5); - System.arraycopy(buffer, index, buffer, index + 5, size - index); - buffer[index++] = 'f'; - buffer[index++] = 'a'; - buffer[index++] = 'l'; - buffer[index++] = 's'; - buffer[index] = 'e'; - size += 5; - } - return this; - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final char value) { - validateIndex(index); - ensureCapacity(size + 1); - System.arraycopy(buffer, index, buffer, index + 1, size - index); - buffer[index] = value; - size++; - return this; - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final int value) { - return insert(index, String.valueOf(value)); - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final long value) { - return insert(index, String.valueOf(value)); - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final float value) { - return insert(index, String.valueOf(value)); - } - - /** - * Inserts the value into this builder. - * - * @param index the index to add at, must be valid - * @param value the value to insert - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder insert(final int index, final double value) { - return insert(index, String.valueOf(value)); - } - - /** - * Internal method to delete a range without validation. - * - * @param startIndex the start index, must be valid - * @param endIndex the end index (exclusive), must be valid - * @param len the length, must be valid - * @throws IndexOutOfBoundsException if any index is invalid - */ - private void deleteImpl(final int startIndex, final int endIndex, final int len) { - System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex); - size -= len; - } - - /** - * Deletes the characters between the two specified indices. - * - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except - * that if too large it is treated as end of string - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder delete(final int startIndex, int endIndex) { - endIndex = validateRange(startIndex, endIndex); - final int len = endIndex - startIndex; - if (len > 0) { - deleteImpl(startIndex, endIndex, len); - } - return this; - } - - /** - * Deletes the character wherever it occurs in the builder. - * - * @param ch the character to delete - * @return this, to enable chaining - */ - public StrBuilder deleteAll(final char ch) { - for (int i = 0; i < size; i++) { - if (buffer[i] == ch) { - final int start = i; - while (++i < size) { - if (buffer[i] != ch) { - break; - } - } - final int len = i - start; - deleteImpl(start, i, len); - i -= len; - } - } - return this; - } - - /** - * Deletes the character wherever it occurs in the builder. - * - * @param ch the character to delete - * @return this, to enable chaining - */ - public StrBuilder deleteFirst(final char ch) { - for (int i = 0; i < size; i++) { - if (buffer[i] == ch) { - deleteImpl(i, i + 1, 1); - break; - } - } - return this; - } - - /** - * Deletes the string wherever it occurs in the builder. - * - * @param str the string to delete, null causes no action - * @return this, to enable chaining - */ - public StrBuilder deleteAll(final String str) { - final int len = StringUtils.length(str); - if (len > 0) { - int index = indexOf(str, 0); - while (index >= 0) { - deleteImpl(index, index + len, len); - index = indexOf(str, index); - } - } - return this; - } - - /** - * Deletes the string wherever it occurs in the builder. - * - * @param str the string to delete, null causes no action - * @return this, to enable chaining - */ - public StrBuilder deleteFirst(final String str) { - final int len = StringUtils.length(str); - if (len > 0) { - final int index = indexOf(str, 0); - if (index >= 0) { - deleteImpl(index, index + len, len); - } - } - return this; - } - - /** - * Deletes all parts of the builder that the matcher matches. - *

- * Matchers can be used to perform advanced deletion behavior. - * For example you could write a matcher to delete all occurrences - * where the character 'a' is followed by a number. - *

- * - * @param matcher the matcher to use to find the deletion, null causes no action - * @return this, to enable chaining - */ - public StrBuilder deleteAll(final StrMatcher matcher) { - return replace(matcher, null, 0, size, -1); - } - - /** - * Deletes the first match within the builder using the specified matcher. - *

- * Matchers can be used to perform advanced deletion behavior. - * For example you could write a matcher to delete - * where the character 'a' is followed by a number. - *

- * - * @param matcher the matcher to use to find the deletion, null causes no action - * @return this, to enable chaining - */ - public StrBuilder deleteFirst(final StrMatcher matcher) { - return replace(matcher, null, 0, size, 1); - } - - /** - * Internal method to delete a range without validation. - * - * @param startIndex the start index, must be valid - * @param endIndex the end index (exclusive), must be valid - * @param removeLen the length to remove (endIndex - startIndex), must be valid - * @param insertStr the string to replace with, null means delete range - * @param insertLen the length of the insert string, must be valid - * @throws IndexOutOfBoundsException if any index is invalid - */ - private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, final int insertLen) { - final int newSize = size - removeLen + insertLen; - if (insertLen != removeLen) { - ensureCapacity(newSize); - System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, size - endIndex); - size = newSize; - } - if (insertLen > 0) { - insertStr.getChars(0, insertLen, buffer, startIndex); + appendable.append(this); } } /** - * Replaces a portion of the string builder with another string. - * The length of the inserted string does not have to match the removed length. + * Appends an iterable placing separators between each value, but + * not before the first or after the last. + * Appending a null iterable will have no effect. + * Each object is appended using {@link #append(Object)}. * - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except - * that if too large it is treated as end of string - * @param replaceStr the string to replace with, null means delete range - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid - */ - public StrBuilder replace(final int startIndex, int endIndex, final String replaceStr) { - endIndex = validateRange(startIndex, endIndex); - final int insertLen = StringUtils.length(replaceStr); - replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen); - return this; - } - - /** - * Replaces the search character with the replace character - * throughout the builder. - * - * @param search the search character - * @param replace the replace character + * @param iterable the iterable to append + * @param separator the separator to use, null means no separator * @return this, to enable chaining */ - public StrBuilder replaceAll(final char search, final char replace) { - if (search != replace) { - for (int i = 0; i < size; i++) { - if (buffer[i] == search) { - buffer[i] = replace; + public StrBuilder appendWithSeparators(final Iterable iterable, final String separator) { + if (iterable != null) { + final String sep = Objects.toString(separator, ""); + final Iterator it = iterable.iterator(); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); } } } @@ -1979,19 +1408,22 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Replaces the first instance of the search character with the - * replace character in the builder. + * Appends an iterator placing separators between each value, but + * not before the first or after the last. + * Appending a null iterator will have no effect. + * Each object is appended using {@link #append(Object)}. * - * @param search the search character - * @param replace the replace character + * @param it the iterator to append + * @param separator the separator to use, null means no separator * @return this, to enable chaining */ - public StrBuilder replaceFirst(final char search, final char replace) { - if (search != replace) { - for (int i = 0; i < size; i++) { - if (buffer[i] == search) { - buffer[i] = replace; - break; + public StrBuilder appendWithSeparators(final Iterator it, final String separator) { + if (it != null) { + final String sep = Objects.toString(separator, ""); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); } } } @@ -1999,365 +1431,176 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Replaces the search string with the replace string throughout the builder. + * Appends an array placing separators between each value, but + * not before the first or after the last. + * Appending a null array will have no effect. + * Each object is appended using {@link #append(Object)}. * - * @param searchStr the search string, null causes no action to occur - * @param replaceStr the replace string, null is equivalent to an empty string + * @param array the array to append + * @param separator the separator to use, null means no separator * @return this, to enable chaining */ - public StrBuilder replaceAll(final String searchStr, final String replaceStr) { - final int searchLen = StringUtils.length(searchStr); - if (searchLen > 0) { - final int replaceLen = StringUtils.length(replaceStr); - int index = indexOf(searchStr, 0); - while (index >= 0) { - replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); - index = indexOf(searchStr, index + replaceLen); + public StrBuilder appendWithSeparators(final Object[] array, final String separator) { + if (array != null && array.length > 0) { + final String sep = Objects.toString(separator, ""); + append(array[0]); + for (int i = 1; i < array.length; i++) { + append(sep); + append(array[i]); } } return this; } /** - * Replaces the first instance of the search string with the replace string. - * - * @param searchStr the search string, null causes no action to occur - * @param replaceStr the replace string, null is equivalent to an empty string - * @return this, to enable chaining - */ - public StrBuilder replaceFirst(final String searchStr, final String replaceStr) { - final int searchLen = StringUtils.length(searchStr); - if (searchLen > 0) { - final int index = indexOf(searchStr, 0); - if (index >= 0) { - final int replaceLen = StringUtils.length(replaceStr); - replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); - } - } - return this; - } - - /** - * Replaces all matches within the builder with the replace string. + * Gets the contents of this builder as a Reader. *

- * Matchers can be used to perform advanced replace behavior. - * For example you could write a matcher to replace all occurrences - * where the character 'a' is followed by a number. + * This method allows the contents of the builder to be read + * using any standard method that expects a Reader. + *

+ *

+ * To use, simply create a {@link StrBuilder}, populate it with + * data, call {@code asReader}, and then read away. + *

+ *

+ * The internal character array is shared between the builder and the reader. + * This allows you to append to the builder after creating the reader, + * and the changes will be picked up. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the reader in one thread. + *

+ *

+ * The returned reader supports marking, and ignores the flush method. *

* - * @param matcher the matcher to use to find the deletion, null causes no action - * @param replaceStr the replace string, null is equivalent to an empty string - * @return this, to enable chaining + * @return a reader that reads from this builder */ - public StrBuilder replaceAll(final StrMatcher matcher, final String replaceStr) { - return replace(matcher, replaceStr, 0, size, -1); + public Reader asReader() { + return new StrBuilderReader(); } /** - * Replaces the first match within the builder with the replace string. + * Creates a tokenizer that can tokenize the contents of this builder. *

- * Matchers can be used to perform advanced replace behavior. - * For example you could write a matcher to replace - * where the character 'a' is followed by a number. + * This method allows the contents of this builder to be tokenized. + * The tokenizer will be setup by default to tokenize on space, tab, + * newline and formfeed (as per StringTokenizer). These values can be + * changed on the tokenizer class, before retrieving the tokens. + *

+ *

+ * The returned tokenizer is linked to this builder. You may intermix + * calls to the builder and tokenizer within certain limits, however + * there is no synchronization. Once the tokenizer has been used once, + * it must be {@link StrTokenizer#reset() reset} to pickup the latest + * changes in the builder. For example: + *

+ *
+     * StrBuilder b = new StrBuilder();
+     * b.append("a b ");
+     * StrTokenizer t = b.asTokenizer();
+     * String[] tokens1 = t.getTokenArray();  // returns a,b
+     * b.append("c d ");
+     * String[] tokens2 = t.getTokenArray();  // returns a,b (c and d ignored)
+     * t.reset();              // reset causes builder changes to be picked up
+     * String[] tokens3 = t.getTokenArray();  // returns a,b,c,d
+     * 
+ *

+ * In addition to simply intermixing appends and tokenization, you can also + * call the set methods on the tokenizer to alter how it tokenizes. Just + * remember to call reset when you want to pickup builder changes. + *

+ *

+ * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])} + * with a non-null value will break the link with the builder. *

* - * @param matcher the matcher to use to find the deletion, null causes no action - * @param replaceStr the replace string, null is equivalent to an empty string - * @return this, to enable chaining + * @return a tokenizer that is linked to this builder */ - public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) { - return replace(matcher, replaceStr, 0, size, 1); + public StrTokenizer asTokenizer() { + return new StrBuilderTokenizer(); } /** - * Advanced search and replaces within the builder using a matcher. + * Gets this builder as a Writer that can be written to. *

- * Matchers can be used to perform advanced behavior. - * For example you could write a matcher to delete all occurrences - * where the character 'a' is followed by a number. + * This method allows you to populate the contents of the builder + * using any standard method that takes a Writer. + *

+ *

+ * To use, simply create a {@link StrBuilder}, + * call {@code asWriter}, and populate away. The data is available + * at any time using the methods of the {@link StrBuilder}. + *

+ *

+ * The internal character array is shared between the builder and the writer. + * This allows you to intermix calls that append to the builder and + * write using the writer and the changes will be occur correctly. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the writer in one thread. + *

+ *

+ * The returned writer ignores the close and flush methods. *

* - * @param matcher the matcher to use to find the deletion, null causes no action - * @param replaceStr the string to replace the match with, null is a delete - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except - * that if too large it is treated as end of string - * @param replaceCount the number of times to replace, -1 for replace all - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if start index is invalid + * @return a writer that populates this builder */ - public StrBuilder replace( - final StrMatcher matcher, final String replaceStr, - final int startIndex, int endIndex, final int replaceCount) { - endIndex = validateRange(startIndex, endIndex); - return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + public Writer asWriter() { + return new StrBuilderWriter(); } /** - * Replaces within the builder using a matcher. - *

- * Matchers can be used to perform advanced behavior. - * For example you could write a matcher to delete all occurrences - * where the character 'a' is followed by a number. - *

- * - * @param matcher the matcher to use to find the deletion, null causes no action - * @param replaceStr the string to replace the match with, null is a delete - * @param from the start index, must be valid - * @param to the end index (exclusive), must be valid - * @param replaceCount the number of times to replace, -1 for replace all - * @return this, to enable chaining - * @throws IndexOutOfBoundsException if any index is invalid - */ - private StrBuilder replaceImpl( - final StrMatcher matcher, final String replaceStr, - final int from, int to, int replaceCount) { - if (matcher == null || size == 0) { - return this; - } - final int replaceLen = StringUtils.length(replaceStr); - for (int i = from; i < to && replaceCount != 0; i++) { - final char[] buf = buffer; - final int removeLen = matcher.isMatch(buf, i, from, to); - if (removeLen > 0) { - replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen); - to = to - removeLen + replaceLen; - i = i + replaceLen - 1; - if (replaceCount > 0) { - replaceCount--; - } - } - } - return this; - } - - /** - * Reverses the string builder placing each character in the opposite index. - * - * @return this, to enable chaining - */ - public StrBuilder reverse() { - if (size == 0) { - return this; - } - - final int half = size / 2; - final char[] buf = buffer; - for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++, rightIdx--) { - final char swap = buf[leftIdx]; - buf[leftIdx] = buf[rightIdx]; - buf[rightIdx] = swap; - } - return this; - } - - /** - * Trims the builder by removing characters less than or equal to a space - * from the beginning and end. - * - * @return this, to enable chaining - */ - public StrBuilder trim() { - if (size == 0) { - return this; - } - int len = size; - final char[] buf = buffer; - int pos = 0; - while (pos < len && buf[pos] <= ' ') { - pos++; - } - while (pos < len && buf[len - 1] <= ' ') { - len--; - } - if (len < size) { - delete(len, size); - } - if (pos > 0) { - delete(0, pos); - } - return this; - } - - /** - * Checks whether this builder starts with the specified string. - *

- * Note that this method handles null input quietly, unlike String. - *

- * - * @param str the string to search for, null returns false - * @return true if the builder starts with the string - */ - public boolean startsWith(final String str) { - if (str == null) { - return false; - } - final int len = str.length(); - if (len == 0) { - return true; - } - if (len > size) { - return false; - } - for (int i = 0; i < len; i++) { - if (buffer[i] != str.charAt(i)) { - return false; - } - } - return true; - } - - /** - * Checks whether this builder ends with the specified string. - *

- * Note that this method handles null input quietly, unlike String. - *

- * - * @param str the string to search for, null returns false - * @return true if the builder ends with the string - */ - public boolean endsWith(final String str) { - if (str == null) { - return false; - } - final int len = str.length(); - if (len == 0) { - return true; - } - if (len > size) { - return false; - } - int pos = size - len; - for (int i = 0; i < len; i++, pos++) { - if (buffer[pos] != str.charAt(i)) { - return false; - } - } - return true; - } - - /** - * {@inheritDoc} - * @since 3.0 + * Implement the {@link Builder} interface. + * @return the builder as a String + * @since 3.2 + * @see #toString() */ @Override - public CharSequence subSequence(final int startIndex, final int endIndex) { - if (startIndex < 0) { - throw new StringIndexOutOfBoundsException(startIndex); - } - if (endIndex > size) { - throw new StringIndexOutOfBoundsException(endIndex); - } - if (startIndex > endIndex) { - throw new StringIndexOutOfBoundsException(endIndex - startIndex); - } - return substring(startIndex, endIndex); + public String build() { + return toString(); } /** - * Extracts a portion of this string builder as a string. + * Gets the current size of the internal character array buffer. * - * @param start the start index, inclusive, must be valid - * @return the new string + * @return the capacity + */ + public int capacity() { + return buffer.length; + } + + /** + * Gets the character at the specified index. + * + * @see #setCharAt(int, char) + * @see #deleteCharAt(int) + * @param index the index to retrieve, must be valid + * @return the character at the index * @throws IndexOutOfBoundsException if the index is invalid */ - public String substring(final int start) { - return substring(start, size); + @Override + public char charAt(final int index) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + return buffer[index]; } /** - * Extracts a portion of this string builder as a string. + * Clears the string builder (convenience Collections API style method). *

- * Note: This method treats an endIndex greater than the length of the - * builder as equal to the length of the builder, and continues - * without error, unlike StringBuffer or String. + * This method does not reduce the size of the internal character buffer. + * To do that, call {@code clear()} followed by {@link #minimizeCapacity()}. + *

+ *

+ * This method is the same as {@link #setLength(int)} called with zero + * and is provided to match the API of Collections. *

* - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except - * that if too large it is treated as end of string - * @return the new string - * @throws IndexOutOfBoundsException if the index is invalid + * @return this, to enable chaining */ - public String substring(final int startIndex, int endIndex) { - endIndex = validateRange(startIndex, endIndex); - return new String(buffer, startIndex, endIndex - startIndex); - } - - /** - * Extracts the leftmost characters from the string builder without - * throwing an exception. - *

- * This method extracts the left {@code length} characters from - * the builder. If this many characters are not available, the whole - * builder is returned. Thus the returned string may be shorter than the - * length requested. - *

- * - * @param length the number of characters to extract, negative returns empty string - * @return the new string - */ - public String leftString(final int length) { - if (length <= 0) { - return StringUtils.EMPTY; - } - if (length >= size) { - return new String(buffer, 0, size); - } - return new String(buffer, 0, length); - } - - /** - * Extracts the rightmost characters from the string builder without - * throwing an exception. - *

- * This method extracts the right {@code length} characters from - * the builder. If this many characters are not available, the whole - * builder is returned. Thus the returned string may be shorter than the - * length requested. - *

- * - * @param length the number of characters to extract, negative returns empty string - * @return the new string - */ - public String rightString(final int length) { - if (length <= 0) { - return StringUtils.EMPTY; - } - if (length >= size) { - return new String(buffer, 0, size); - } - return new String(buffer, size - length, length); - } - - /** - * Extracts some characters from the middle of the string builder without - * throwing an exception. - *

- * This method extracts {@code length} characters from the builder - * at the specified index. - * If the index is negative it is treated as zero. - * If the index is greater than the builder size, it is treated as the builder size. - * If the length is negative, the empty string is returned. - * If insufficient characters are available in the builder, as much as possible is returned. - * Thus the returned string may be shorter than the length requested. - *

- * - * @param index the index to start at, negative means zero - * @param length the number of characters to extract, negative returns empty string - * @return the new string - */ - public String midString(int index, final int length) { - if (index < 0) { - index = 0; - } - if (length <= 0 || index >= size) { - return StringUtils.EMPTY; - } - if (size <= index + length) { - return new String(buffer, index, size - index); - } - return new String(buffer, index, length); + public StrBuilder clear() { + size = 0; + return this; } /** @@ -2401,6 +1644,337 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build public boolean contains(final StrMatcher matcher) { return indexOf(matcher, 0) >= 0; } + /** + * Deletes the characters between the two specified indices. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder delete(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len > 0) { + deleteImpl(startIndex, endIndex, len); + } + return this; + } + + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch the character to delete + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + final int start = i; + while (++i < size) { + if (buffer[i] != ch) { + break; + } + } + final int len = i - start; + deleteImpl(start, i, len); + i -= len; + } + } + return this; + } + + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str the string to delete, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final String str) { + final int len = StringUtils.length(str); + if (len > 0) { + int index = indexOf(str, 0); + while (index >= 0) { + deleteImpl(index, index + len, len); + index = indexOf(str, index); + } + } + return this; + } + + /** + * Deletes all parts of the builder that the matcher matches. + *

+ * Matchers can be used to perform advanced deletion behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + *

+ * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteAll(final StrMatcher matcher) { + return replace(matcher, null, 0, size, -1); + } + + /** + * Deletes the character at the specified index. + * + * @see #charAt(int) + * @see #setCharAt(int, char) + * @param index the index to delete + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder deleteCharAt(final int index) { + if (index < 0 || index >= size) { + throw new StringIndexOutOfBoundsException(index); + } + deleteImpl(index, index + 1, 1); + return this; + } + + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch the character to delete + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + deleteImpl(i, i + 1, 1); + break; + } + } + return this; + } + + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str the string to delete, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final String str) { + final int len = StringUtils.length(str); + if (len > 0) { + final int index = indexOf(str, 0); + if (index >= 0) { + deleteImpl(index, index + len, len); + } + } + return this; + } + + /** + * Deletes the first match within the builder using the specified matcher. + *

+ * Matchers can be used to perform advanced deletion behavior. + * For example you could write a matcher to delete + * where the character 'a' is followed by a number. + *

+ * + * @param matcher the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public StrBuilder deleteFirst(final StrMatcher matcher) { + return replace(matcher, null, 0, size, 1); + } + + /** + * Internal method to delete a range without validation. + * + * @param startIndex the start index, must be valid + * @param endIndex the end index (exclusive), must be valid + * @param len the length, must be valid + * @throws IndexOutOfBoundsException if any index is invalid + */ + private void deleteImpl(final int startIndex, final int endIndex, final int len) { + System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex); + size -= len; + } + + /** + * Checks whether this builder ends with the specified string. + *

+ * Note that this method handles null input quietly, unlike String. + *

+ * + * @param str the string to search for, null returns false + * @return true if the builder ends with the string + */ + public boolean endsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + int pos = size - len; + for (int i = 0; i < len; i++, pos++) { + if (buffer[pos] != str.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Checks the capacity and ensures that it is at least the size specified. + * + * @param capacity the capacity to ensure + * @return this, to enable chaining + */ + public StrBuilder ensureCapacity(final int capacity) { + if (capacity > buffer.length) { + final char[] old = buffer; + buffer = new char[capacity * 2]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param obj the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + @Override + public boolean equals(final Object obj) { + return obj instanceof StrBuilder && equals((StrBuilder) obj); + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equals(final StrBuilder other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (this.size != other.size) { + return false; + } + final char[] thisBuf = this.buffer; + final char[] otherBuf = other.buffer; + for (int i = size - 1; i >= 0; i--) { + if (thisBuf[i] != otherBuf[i]) { + return false; + } + } + return true; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content ignoring case. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equalsIgnoreCase(final StrBuilder other) { + if (this == other) { + return true; + } + if (this.size != other.size) { + return false; + } + final char[] thisBuf = this.buffer; + final char[] otherBuf = other.buffer; + for (int i = size - 1; i >= 0; i--) { + final char c1 = thisBuf[i]; + final char c2 = otherBuf[i]; + if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { + return false; + } + } + return true; + } + + /** + * Copies the character array into the specified array. + * + * @param destination the destination array, null will cause an array to be created + * @return the input array, unless that was null or too small + */ + public char[] getChars(char[] destination) { + final int len = length(); + if (destination == null || destination.length < len) { + destination = new char[len]; + } + System.arraycopy(buffer, 0, destination, 0, len); + return destination; + } + + /** + * Copies the character array into the specified array. + * + * @param startIndex first index to copy, inclusive, must be valid + * @param endIndex last index, exclusive, must be valid + * @param destination the destination array, must not be null or too small + * @param destinationIndex the index to start copying in destination + * @throws NullPointerException if the array is null + * @throws IndexOutOfBoundsException if any index is invalid + */ + public void getChars(final int startIndex, final int endIndex, final char[] destination, final int destinationIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex < 0 || endIndex > length()) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); + } + + /** + * Gets the text to be appended when a new line is added. + * + * @return the new line text, null means use system default + */ + public String getNewLineText() { + return newLine; + } + + /** + * Gets the text to be appended when null is added. + * + * @return the null text, null means no append + */ + public String getNullText() { + return nullText; + } + + /** + * Gets a suitable hash code for this builder. + * + * @return a hash code + */ + @Override + public int hashCode() { + final char[] buf = buffer; + int hash = 0; + for (int i = size - 1; i >= 0; i--) { + hash = 31 * hash + buf[i]; + } + return hash; + } /** * Searches the string builder to find the first reference to the specified char. @@ -2504,6 +2078,227 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build return -1; } + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(int index, final boolean value) { + validateIndex(index); + if (value) { + ensureCapacity(size + 4); + System.arraycopy(buffer, index, buffer, index + 4, size - index); + buffer[index++] = 't'; + buffer[index++] = 'r'; + buffer[index++] = 'u'; + buffer[index] = 'e'; + size += 4; + } else { + ensureCapacity(size + 5); + System.arraycopy(buffer, index, buffer, index + 5, size - index); + buffer[index++] = 'f'; + buffer[index++] = 'a'; + buffer[index++] = 'l'; + buffer[index++] = 's'; + buffer[index] = 'e'; + size += 5; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final char value) { + validateIndex(index); + ensureCapacity(size + 1); + System.arraycopy(buffer, index, buffer, index + 1, size - index); + buffer[index] = value; + size++; + return this; + } + + /** + * Inserts the character array into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param chars the char array to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final char[] chars) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + final int len = chars.length; + if (len > 0) { + ensureCapacity(size + len); + System.arraycopy(buffer, index, buffer, index + len, size - index); + System.arraycopy(chars, 0, buffer, index, len); + size += len; + } + return this; + } + + /** + * Inserts part of the character array into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param chars the char array to insert + * @param offset the offset into the character array to start at, must be valid + * @param length the length of the character array part to copy, must be positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if any index is invalid + */ + public StrBuilder insert(final int index, final char[] chars, final int offset, final int length) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + if (offset < 0 || offset > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid offset: " + offset); + } + if (length < 0 || offset + length > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); + } + if (length > 0) { + ensureCapacity(size + length); + System.arraycopy(buffer, index, buffer, index + length, size - index); + System.arraycopy(chars, offset, buffer, index, length); + size += length; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final double value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final float value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final int value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index the index to add at, must be valid + * @param value the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final long value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the string representation of an object into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param obj the object to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, final Object obj) { + if (obj == null) { + return insert(index, nullText); + } + return insert(index, obj.toString()); + } + + /** + * Inserts the string into this builder. + * Inserting null will use the stored null text value. + * + * @param index the index to add at, must be valid + * @param str the string to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder insert(final int index, String str) { + validateIndex(index); + if (str == null) { + str = nullText; + } + if (str != null) { + final int strLen = str.length(); + if (strLen > 0) { + final int newSize = size + strLen; + ensureCapacity(newSize); + System.arraycopy(buffer, index, buffer, index + strLen, size - index); + size = newSize; + str.getChars(0, strLen, buffer, index); + } + } + return this; + } + + /** + * Checks is the string builder is empty (convenience Collections API style method). + *

+ * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + *

+ * + * @return {@code true} if the size is {@code 0}. + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Checks is the string builder is not empty (convenience Collections API style method). + *

+ * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + *

+ * + * @return {@code true} if the size is greater than {@code 0}. + * @since 3.12.0 + */ + public boolean isNotEmpty() { + return size > 0; + } + /** * Searches the string builder to find the last reference to the specified char. * @@ -2606,206 +2401,568 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Creates a tokenizer that can tokenize the contents of this builder. + * Extracts the leftmost characters from the string builder without + * throwing an exception. *

- * This method allows the contents of this builder to be tokenized. - * The tokenizer will be setup by default to tokenize on space, tab, - * newline and formfeed (as per StringTokenizer). These values can be - * changed on the tokenizer class, before retrieving the tokens. - *

- *

- * The returned tokenizer is linked to this builder. You may intermix - * calls to the builder and tokenizer within certain limits, however - * there is no synchronization. Once the tokenizer has been used once, - * it must be {@link StrTokenizer#reset() reset} to pickup the latest - * changes in the builder. For example: - *

- *
-     * StrBuilder b = new StrBuilder();
-     * b.append("a b ");
-     * StrTokenizer t = b.asTokenizer();
-     * String[] tokens1 = t.getTokenArray();  // returns a,b
-     * b.append("c d ");
-     * String[] tokens2 = t.getTokenArray();  // returns a,b (c and d ignored)
-     * t.reset();              // reset causes builder changes to be picked up
-     * String[] tokens3 = t.getTokenArray();  // returns a,b,c,d
-     * 
- *

- * In addition to simply intermixing appends and tokenization, you can also - * call the set methods on the tokenizer to alter how it tokenizes. Just - * remember to call reset when you want to pickup builder changes. - *

- *

- * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])} - * with a non-null value will break the link with the builder. + * This method extracts the left {@code length} characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. *

* - * @return a tokenizer that is linked to this builder + * @param length the number of characters to extract, negative returns empty string + * @return the new string */ - public StrTokenizer asTokenizer() { - return new StrBuilderTokenizer(); + public String leftString(final int length) { + if (length <= 0) { + return StringUtils.EMPTY; + } + if (length >= size) { + return new String(buffer, 0, size); + } + return new String(buffer, 0, length); } /** - * Gets the contents of this builder as a Reader. - *

- * This method allows the contents of the builder to be read - * using any standard method that expects a Reader. - *

- *

- * To use, simply create a {@link StrBuilder}, populate it with - * data, call {@code asReader}, and then read away. - *

- *

- * The internal character array is shared between the builder and the reader. - * This allows you to append to the builder after creating the reader, - * and the changes will be picked up. - * Note however, that no synchronization occurs, so you must perform - * all operations with the builder and the reader in one thread. - *

- *

- * The returned reader supports marking, and ignores the flush method. - *

+ * Gets the length of the string builder. * - * @return a reader that reads from this builder + * @return the length */ - public Reader asReader() { - return new StrBuilderReader(); + @Override + public int length() { + return size; } /** - * Gets this builder as a Writer that can be written to. + * Extracts some characters from the middle of the string builder without + * throwing an exception. *

- * This method allows you to populate the contents of the builder - * using any standard method that takes a Writer. - *

- *

- * To use, simply create a {@link StrBuilder}, - * call {@code asWriter}, and populate away. The data is available - * at any time using the methods of the {@link StrBuilder}. - *

- *

- * The internal character array is shared between the builder and the writer. - * This allows you to intermix calls that append to the builder and - * write using the writer and the changes will be occur correctly. - * Note however, that no synchronization occurs, so you must perform - * all operations with the builder and the writer in one thread. - *

- *

- * The returned writer ignores the close and flush methods. + * This method extracts {@code length} characters from the builder + * at the specified index. + * If the index is negative it is treated as zero. + * If the index is greater than the builder size, it is treated as the builder size. + * If the length is negative, the empty string is returned. + * If insufficient characters are available in the builder, as much as possible is returned. + * Thus the returned string may be shorter than the length requested. *

* - * @return a writer that populates this builder + * @param index the index to start at, negative means zero + * @param length the number of characters to extract, negative returns empty string + * @return the new string */ - public Writer asWriter() { - return new StrBuilderWriter(); + public String midString(int index, final int length) { + if (index < 0) { + index = 0; + } + if (length <= 0 || index >= size) { + return StringUtils.EMPTY; + } + if (size <= index + length) { + return new String(buffer, index, size - index); + } + return new String(buffer, index, length); } /** - * Appends current contents of this {@link StrBuilder} to the - * provided {@link Appendable}. - *

- * This method tries to avoid doing any extra copies of contents. - *

+ * Minimizes the capacity to the actual length of the string. * - * @param appendable the appendable to append data to - * @throws IOException if an I/O error occurs + * @return this, to enable chaining + */ + public StrBuilder minimizeCapacity() { + if (buffer.length > length()) { + final char[] old = buffer; + buffer = new char[length()]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + /** + * If possible, reads chars from the provided {@link Readable} directly into underlying + * character buffer without making extra copies. + * + * @param readable object to read from + * @return the number of characters read + * @throws IOException if an I/O error occurs. * * @since 3.4 - * @see #readFrom(Readable) + * @see #appendTo(Appendable) */ - public void appendTo(final Appendable appendable) throws IOException { - if (appendable instanceof Writer) { - ((Writer) appendable).write(buffer, 0, size); - } else if (appendable instanceof StringBuilder) { - ((StringBuilder) appendable).append(buffer, 0, size); - } else if (appendable instanceof StringBuffer) { - ((StringBuffer) appendable).append(buffer, 0, size); - } else if (appendable instanceof CharBuffer) { - ((CharBuffer) appendable).put(buffer, 0, size); + public int readFrom(final Readable readable) throws IOException { + final int oldSize = size; + if (readable instanceof Reader) { + final Reader r = (Reader) readable; + ensureCapacity(size + 1); + int read; + while ((read = r.read(buffer, size, buffer.length - size)) != -1) { + size += read; + ensureCapacity(size + 1); + } + } else if (readable instanceof CharBuffer) { + final CharBuffer cb = (CharBuffer) readable; + final int remaining = cb.remaining(); + ensureCapacity(size + remaining); + cb.get(buffer, size, remaining); + size += remaining; } else { - appendable.append(this); - } - } - - /** - * Checks the contents of this builder against another to see if they - * contain the same character content ignoring case. - * - * @param other the object to check, null returns false - * @return true if the builders contain the same characters in the same order - */ - public boolean equalsIgnoreCase(final StrBuilder other) { - if (this == other) { - return true; - } - if (this.size != other.size) { - return false; - } - final char[] thisBuf = this.buffer; - final char[] otherBuf = other.buffer; - for (int i = size - 1; i >= 0; i--) { - final char c1 = thisBuf[i]; - final char c2 = otherBuf[i]; - if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { - return false; + while (true) { + ensureCapacity(size + 1); + final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size); + final int read = readable.read(buf); + if (read == -1) { + break; + } + size += read; } } - return true; + return size - oldSize; } /** - * Checks the contents of this builder against another to see if they - * contain the same character content. + * Replaces a portion of the string builder with another string. + * The length of the inserted string does not have to match the removed length. * - * @param other the object to check, null returns false - * @return true if the builders contain the same characters in the same order + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @param replaceStr the string to replace with, null means delete range + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid */ - public boolean equals(final StrBuilder other) { - if (this == other) { - return true; - } - if (other == null) { - return false; - } - if (this.size != other.size) { - return false; - } - final char[] thisBuf = this.buffer; - final char[] otherBuf = other.buffer; - for (int i = size - 1; i >= 0; i--) { - if (thisBuf[i] != otherBuf[i]) { - return false; + public StrBuilder replace(final int startIndex, int endIndex, final String replaceStr) { + endIndex = validateRange(startIndex, endIndex); + final int insertLen = StringUtils.length(replaceStr); + replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen); + return this; + } + + /** + * Advanced search and replaces within the builder using a matcher. + *

+ * Matchers can be used to perform advanced behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + *

+ * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if start index is invalid + */ + public StrBuilder replace( + final StrMatcher matcher, final String replaceStr, + final int startIndex, int endIndex, final int replaceCount) { + endIndex = validateRange(startIndex, endIndex); + return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + } + + /** + * Replaces the search character with the replace character + * throughout the builder. + * + * @param search the search character + * @param replace the replace character + * @return this, to enable chaining + */ + public StrBuilder replaceAll(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + } } } - return true; + return this; } /** - * Checks the contents of this builder against another to see if they - * contain the same character content. + * Replaces the search string with the replace string throughout the builder. * - * @param obj the object to check, null returns false - * @return true if the builders contain the same characters in the same order + * @param searchStr the search string, null causes no action to occur + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining */ - @Override - public boolean equals(final Object obj) { - return obj instanceof StrBuilder && equals((StrBuilder) obj); + public StrBuilder replaceAll(final String searchStr, final String replaceStr) { + final int searchLen = StringUtils.length(searchStr); + if (searchLen > 0) { + final int replaceLen = StringUtils.length(replaceStr); + int index = indexOf(searchStr, 0); + while (index >= 0) { + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + index = indexOf(searchStr, index + replaceLen); + } + } + return this; } /** - * Gets a suitable hash code for this builder. + * Replaces all matches within the builder with the replace string. + *

+ * Matchers can be used to perform advanced replace behavior. + * For example you could write a matcher to replace all occurrences + * where the character 'a' is followed by a number. + *

* - * @return a hash code + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining */ - @Override - public int hashCode() { + public StrBuilder replaceAll(final StrMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, -1); + } + + /** + * Replaces the first instance of the search character with the + * replace character in the builder. + * + * @param search the search character + * @param replace the replace character + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + break; + } + } + } + return this; + } + + /** + * Replaces the first instance of the search string with the replace string. + * + * @param searchStr the search string, null causes no action to occur + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final String searchStr, final String replaceStr) { + final int searchLen = StringUtils.length(searchStr); + if (searchLen > 0) { + final int index = indexOf(searchStr, 0); + if (index >= 0) { + final int replaceLen = StringUtils.length(replaceStr); + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + } + } + return this; + } + + /** + * Replaces the first match within the builder with the replace string. + *

+ * Matchers can be used to perform advanced replace behavior. + * For example you could write a matcher to replace + * where the character 'a' is followed by a number. + *

+ * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, 1); + } + + /** + * Internal method to delete a range without validation. + * + * @param startIndex the start index, must be valid + * @param endIndex the end index (exclusive), must be valid + * @param removeLen the length to remove (endIndex - startIndex), must be valid + * @param insertStr the string to replace with, null means delete range + * @param insertLen the length of the insert string, must be valid + * @throws IndexOutOfBoundsException if any index is invalid + */ + private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, final int insertLen) { + final int newSize = size - removeLen + insertLen; + if (insertLen != removeLen) { + ensureCapacity(newSize); + System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, size - endIndex); + size = newSize; + } + if (insertLen > 0) { + insertStr.getChars(0, insertLen, buffer, startIndex); + } + } + + /** + * Replaces within the builder using a matcher. + *

+ * Matchers can be used to perform advanced behavior. + * For example you could write a matcher to delete all occurrences + * where the character 'a' is followed by a number. + *

+ * + * @param matcher the matcher to use to find the deletion, null causes no action + * @param replaceStr the string to replace the match with, null is a delete + * @param from the start index, must be valid + * @param to the end index (exclusive), must be valid + * @param replaceCount the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if any index is invalid + */ + private StrBuilder replaceImpl( + final StrMatcher matcher, final String replaceStr, + final int from, int to, int replaceCount) { + if (matcher == null || size == 0) { + return this; + } + final int replaceLen = StringUtils.length(replaceStr); + for (int i = from; i < to && replaceCount != 0; i++) { + final char[] buf = buffer; + final int removeLen = matcher.isMatch(buf, i, from, to); + if (removeLen > 0) { + replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen); + to = to - removeLen + replaceLen; + i = i + replaceLen - 1; + if (replaceCount > 0) { + replaceCount--; + } + } + } + return this; + } + + /** + * Reverses the string builder placing each character in the opposite index. + * + * @return this, to enable chaining + */ + public StrBuilder reverse() { + if (size == 0) { + return this; + } + + final int half = size / 2; final char[] buf = buffer; - int hash = 0; - for (int i = size - 1; i >= 0; i--) { - hash = 31 * hash + buf[i]; + for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++, rightIdx--) { + final char swap = buf[leftIdx]; + buf[leftIdx] = buf[rightIdx]; + buf[rightIdx] = swap; } - return hash; + return this; + } + + /** + * Extracts the rightmost characters from the string builder without + * throwing an exception. + *

+ * This method extracts the right {@code length} characters from + * the builder. If this many characters are not available, the whole + * builder is returned. Thus the returned string may be shorter than the + * length requested. + *

+ * + * @param length the number of characters to extract, negative returns empty string + * @return the new string + */ + public String rightString(final int length) { + if (length <= 0) { + return StringUtils.EMPTY; + } + if (length >= size) { + return new String(buffer, 0, size); + } + return new String(buffer, size - length, length); + } + + /** + * Sets the character at the specified index. + * + * @see #charAt(int) + * @see #deleteCharAt(int) + * @param index the index to set + * @param ch the new character + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the index is invalid + */ + public StrBuilder setCharAt(final int index, final char ch) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + buffer[index] = ch; + return this; + } + + /** + * Updates the length of the builder by either dropping the last characters + * or adding filler of Unicode zero. + * + * @param length the length to set to, must be zero or positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException if the length is negative + */ + public StrBuilder setLength(final int length) { + if (length < 0) { + throw new StringIndexOutOfBoundsException(length); + } + if (length < size) { + size = length; + } else if (length > size) { + ensureCapacity(length); + final int oldEnd = size; + size = length; + for (int i = oldEnd; i < length; i++) { + buffer[i] = CharUtils.NUL; + } + } + return this; + } + + /** + * Sets the text to be appended when a new line is added. + * + * @param newLine the new line text, null means use system default + * @return this, to enable chaining + */ + public StrBuilder setNewLineText(final String newLine) { + this.newLine = newLine; + return this; + } + + /** + * Sets the text to be appended when null is added. + * + * @param nullText the null text, null means no append + * @return this, to enable chaining + */ + public StrBuilder setNullText(String nullText) { + if (StringUtils.isEmpty(nullText)) { + nullText = null; + } + this.nullText = nullText; + return this; + } + + /** + * Gets the length of the string builder. + *

+ * This method is the same as {@link #length()} and is provided to match the + * API of Collections. + *

+ * + * @return the length + */ + public int size() { + return size; + } + + /** + * Checks whether this builder starts with the specified string. + *

+ * Note that this method handles null input quietly, unlike String. + *

+ * + * @param str the string to search for, null returns false + * @return true if the builder starts with the string + */ + public boolean startsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + for (int i = 0; i < len; i++) { + if (buffer[i] != str.charAt(i)) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + * @since 3.0 + */ + @Override + public CharSequence subSequence(final int startIndex, final int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException(endIndex - startIndex); + } + return substring(startIndex, endIndex); + } + + /** + * Extracts a portion of this string builder as a string. + * + * @param start the start index, inclusive, must be valid + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int start) { + return substring(start, size); + } + + /** + * Extracts a portion of this string builder as a string. + *

+ * Note: This method treats an endIndex greater than the length of the + * builder as equal to the length of the builder, and continues + * without error, unlike StringBuffer or String. + *

+ * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except + * that if too large it is treated as end of string + * @return the new string + * @throws IndexOutOfBoundsException if the index is invalid + */ + public String substring(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + return new String(buffer, startIndex, endIndex - startIndex); + } + + /** + * Copies the builder's character array into a new character array. + * + * @return a new array that represents the contents of the builder + */ + public char[] toCharArray() { + if (size == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char[] chars = new char[size]; + System.arraycopy(buffer, 0, chars, 0, size); + return chars; + } + + /** + * Copies part of the builder's character array into a new character array. + * + * @param startIndex the start index, inclusive, must be valid + * @param endIndex the end index, exclusive, must be valid except that + * if too large it is treated as end of string + * @return a new array that holds part of the contents of the builder + * @throws IndexOutOfBoundsException if startIndex is invalid, + * or if endIndex is invalid (but endIndex greater than size is valid) + */ + public char[] toCharArray(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + final char[] chars = new char[len]; + System.arraycopy(buffer, startIndex, chars, 0, len); + return chars; } /** @@ -2845,14 +3002,43 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build } /** - * Implement the {@link Builder} interface. - * @return the builder as a String - * @since 3.2 - * @see #toString() + * Trims the builder by removing characters less than or equal to a space + * from the beginning and end. + * + * @return this, to enable chaining */ - @Override - public String build() { - return toString(); + public StrBuilder trim() { + if (size == 0) { + return this; + } + int len = size; + final char[] buf = buffer; + int pos = 0; + while (pos < len && buf[pos] <= ' ') { + pos++; + } + while (pos < len && buf[len - 1] <= ' ') { + len--; + } + if (len < size) { + delete(len, size); + } + if (pos > 0) { + delete(0, pos); + } + return this; + } + + /** + * Validates parameters defining a single index in the builder. + * + * @param index the index, must be valid + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected void validateIndex(final int index) { + if (index < 0 || index > size) { + throw new StringIndexOutOfBoundsException(index); + } } /** @@ -2877,190 +3063,4 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build return endIndex; } - /** - * Validates parameters defining a single index in the builder. - * - * @param index the index, must be valid - * @throws IndexOutOfBoundsException if the index is invalid - */ - protected void validateIndex(final int index) { - if (index < 0 || index > size) { - throw new StringIndexOutOfBoundsException(index); - } - } - - /** - * Inner class to allow StrBuilder to operate as a tokenizer. - */ - class StrBuilderTokenizer extends StrTokenizer { - - /** - * Default constructor. - */ - StrBuilderTokenizer() { - } - - /** {@inheritDoc} */ - @Override - protected List tokenize(final char[] chars, final int offset, final int count) { - if (chars == null) { - return super.tokenize(StrBuilder.this.buffer, 0, StrBuilder.this.size()); - } - return super.tokenize(chars, offset, count); - } - - /** {@inheritDoc} */ - @Override - public String getContent() { - final String str = super.getContent(); - if (str == null) { - return StrBuilder.this.toString(); - } - return str; - } - } - - /** - * Inner class to allow StrBuilder to operate as a reader. - */ - class StrBuilderReader extends Reader { - /** The current stream position. */ - private int pos; - /** The last mark position. */ - private int mark; - - /** - * Default constructor. - */ - StrBuilderReader() { - } - - /** {@inheritDoc} */ - @Override - public void close() { - // do nothing - } - - /** {@inheritDoc} */ - @Override - public int read() { - if (!ready()) { - return -1; - } - return StrBuilder.this.charAt(pos++); - } - - /** {@inheritDoc} */ - @Override - public int read(final char[] b, final int off, int len) { - if (off < 0 || len < 0 || off > b.length || - off + len > b.length || off + len < 0) { - throw new IndexOutOfBoundsException(); - } - if (len == 0) { - return 0; - } - if (pos >= StrBuilder.this.size()) { - return -1; - } - if (pos + len > size()) { - len = StrBuilder.this.size() - pos; - } - StrBuilder.this.getChars(pos, pos + len, b, off); - pos += len; - return len; - } - - /** {@inheritDoc} */ - @Override - public long skip(long n) { - if (pos + n > StrBuilder.this.size()) { - n = StrBuilder.this.size() - pos; - } - if (n < 0) { - return 0; - } - pos = Math.addExact(pos, Math.toIntExact(n)); - return n; - } - - /** {@inheritDoc} */ - @Override - public boolean ready() { - return pos < StrBuilder.this.size(); - } - - /** {@inheritDoc} */ - @Override - public boolean markSupported() { - return true; - } - - /** {@inheritDoc} */ - @Override - public void mark(final int readAheadLimit) { - mark = pos; - } - - /** {@inheritDoc} */ - @Override - public void reset() { - pos = mark; - } - } - - /** - * Inner class to allow StrBuilder to operate as a writer. - */ - class StrBuilderWriter extends Writer { - - /** - * Default constructor. - */ - StrBuilderWriter() { - } - - /** {@inheritDoc} */ - @Override - public void close() { - // do nothing - } - - /** {@inheritDoc} */ - @Override - public void flush() { - // do nothing - } - - /** {@inheritDoc} */ - @Override - public void write(final int c) { - StrBuilder.this.append((char) c); - } - - /** {@inheritDoc} */ - @Override - public void write(final char[] cbuf) { - StrBuilder.this.append(cbuf); - } - - /** {@inheritDoc} */ - @Override - public void write(final char[] cbuf, final int off, final int len) { - StrBuilder.this.append(cbuf, off, len); - } - - /** {@inheritDoc} */ - @Override - public void write(final String str) { - StrBuilder.this.append(str); - } - - /** {@inheritDoc} */ - @Override - public void write(final String str, final int off, final int len) { - StrBuilder.this.append(str, off, len); - } - } - } diff --git a/src/main/java/org/apache/commons/lang3/text/StrLookup.java b/src/main/java/org/apache/commons/lang3/text/StrLookup.java index 069d68ebf..ca53139a8 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrLookup.java +++ b/src/main/java/org/apache/commons/lang3/text/StrLookup.java @@ -46,91 +46,6 @@ import org.apache.commons.lang3.SystemProperties; @Deprecated public abstract class StrLookup { - /** - * Lookup that always returns null. - */ - private static final StrLookup NONE_LOOKUP = new MapStrLookup<>(null); - - /** - * Lookup based on system properties. - */ - private static final StrLookup SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup(); - - /** - * Returns a lookup which always returns null. - * - * @return a lookup that always returns null, not null - */ - public static StrLookup noneLookup() { - return NONE_LOOKUP; - } - - /** - * Returns a new lookup which uses a copy of the current - * {@link System#getProperties() System properties}. - *

- * If a security manager blocked access to system properties, then null will - * be returned from every lookup. - *

- *

- * If a null key is used, this lookup will throw a NullPointerException. - *

- * - * @return a lookup using system properties, not null - */ - public static StrLookup systemPropertiesLookup() { - return SYSTEM_PROPERTIES_LOOKUP; - } - - /** - * Returns a lookup which looks up values using a map. - *

- * If the map is null, then null will be returned from every lookup. - * The map result object is converted to a string using toString(). - *

- * - * @param the type of the values supported by the lookup - * @param map the map of keys to values, may be null - * @return a lookup using the map, not null - */ - public static StrLookup mapLookup(final Map map) { - return new MapStrLookup<>(map); - } - - /** - * Constructor. - */ - protected StrLookup() { - } - - /** - * Looks up a String key to a String value. - *

- * The internal implementation may use any mechanism to return the value. - * The simplest implementation is to use a Map. However, virtually any - * implementation is possible. - *

- *

- * For example, it would be possible to implement a lookup that used the - * key as a primary key, and looked up the value on demand from the database - * Or, a numeric based implementation could be created that treats the key - * as an integer, increments the value and return the result as a string - - * converting 1 to 2, 15 to 16 etc. - *

- *

- * The {@link #lookup(String)} method always returns a String, regardless of - * the underlying data, by converting it as necessary. For example: - *

- *
-     * Map<String, Object> map = new HashMap<String, Object>();
-     * map.put("number", Integer.valueOf(2));
-     * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
-     * 
- * @param key the key to be looked up, may be null - * @return the matching value, null if no match - */ - public abstract String lookup(String key); - /** * Lookup implementation that uses a Map. * @@ -181,4 +96,89 @@ public abstract class StrLookup { return SystemProperties.getProperty(key); } } + + /** + * Lookup that always returns null. + */ + private static final StrLookup NONE_LOOKUP = new MapStrLookup<>(null); + + /** + * Lookup based on system properties. + */ + private static final StrLookup SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup(); + + /** + * Returns a lookup which looks up values using a map. + *

+ * If the map is null, then null will be returned from every lookup. + * The map result object is converted to a string using toString(). + *

+ * + * @param the type of the values supported by the lookup + * @param map the map of keys to values, may be null + * @return a lookup using the map, not null + */ + public static StrLookup mapLookup(final Map map) { + return new MapStrLookup<>(map); + } + + /** + * Returns a lookup which always returns null. + * + * @return a lookup that always returns null, not null + */ + public static StrLookup noneLookup() { + return NONE_LOOKUP; + } + + /** + * Returns a new lookup which uses a copy of the current + * {@link System#getProperties() System properties}. + *

+ * If a security manager blocked access to system properties, then null will + * be returned from every lookup. + *

+ *

+ * If a null key is used, this lookup will throw a NullPointerException. + *

+ * + * @return a lookup using system properties, not null + */ + public static StrLookup systemPropertiesLookup() { + return SYSTEM_PROPERTIES_LOOKUP; + } + + /** + * Constructor. + */ + protected StrLookup() { + } + + /** + * Looks up a String key to a String value. + *

+ * The internal implementation may use any mechanism to return the value. + * The simplest implementation is to use a Map. However, virtually any + * implementation is possible. + *

+ *

+ * For example, it would be possible to implement a lookup that used the + * key as a primary key, and looked up the value on demand from the database + * Or, a numeric based implementation could be created that treats the key + * as an integer, increments the value and return the result as a string - + * converting 1 to 2, 15 to 16 etc. + *

+ *

+ * The {@link #lookup(String)} method always returns a String, regardless of + * the underlying data, by converting it as necessary. For example: + *

+ *
+     * Map<String, Object> map = new HashMap<String, Object>();
+     * map.put("number", Integer.valueOf(2));
+     * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+     * 
+ * @param key the key to be looked up, may be null + * @return the matching value, null if no match + */ + public abstract String lookup(String key); } diff --git a/src/main/java/org/apache/commons/lang3/text/StrMatcher.java b/src/main/java/org/apache/commons/lang3/text/StrMatcher.java index b29d4fcf4..76b642e95 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrMatcher.java +++ b/src/main/java/org/apache/commons/lang3/text/StrMatcher.java @@ -39,244 +39,35 @@ import org.apache.commons.lang3.StringUtils; public abstract class StrMatcher { /** - * Matches the comma character. + * Class used to define a character for matching purposes. */ - private static final StrMatcher COMMA_MATCHER = new CharMatcher(','); - /** - * Matches the tab character. - */ - private static final StrMatcher TAB_MATCHER = new CharMatcher('\t'); - /** - * Matches the space character. - */ - private static final StrMatcher SPACE_MATCHER = new CharMatcher(' '); - /** - * Matches the same characters as StringTokenizer, - * namely space, tab, newline, formfeed. - */ - private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray()); - /** - * Matches the String trim() whitespace characters. - */ - private static final StrMatcher TRIM_MATCHER = new TrimMatcher(); - /** - * Matches the double quote character. - */ - private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\''); - /** - * Matches the double quote character. - */ - private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"'); - /** - * Matches the single or double quote character. - */ - private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray()); - /** - * Matches no characters. - */ - private static final StrMatcher NONE_MATCHER = new NoMatcher(); + static final class CharMatcher extends StrMatcher { + /** The character to match. */ + private final char ch; - /** - * Returns a matcher which matches the comma character. - * - * @return a matcher for a comma - */ - public static StrMatcher commaMatcher() { - return COMMA_MATCHER; - } - - /** - * Returns a matcher which matches the tab character. - * - * @return a matcher for a tab - */ - public static StrMatcher tabMatcher() { - return TAB_MATCHER; - } - - /** - * Returns a matcher which matches the space character. - * - * @return a matcher for a space - */ - public static StrMatcher spaceMatcher() { - return SPACE_MATCHER; - } - - /** - * Matches the same characters as StringTokenizer, - * namely space, tab, newline and formfeed. - * - * @return the split matcher - */ - public static StrMatcher splitMatcher() { - return SPLIT_MATCHER; - } - - /** - * Matches the String trim() whitespace characters. - * - * @return the trim matcher - */ - public static StrMatcher trimMatcher() { - return TRIM_MATCHER; - } - - /** - * Returns a matcher which matches the single quote character. - * - * @return a matcher for a single quote - */ - public static StrMatcher singleQuoteMatcher() { - return SINGLE_QUOTE_MATCHER; - } - - /** - * Returns a matcher which matches the double quote character. - * - * @return a matcher for a double quote - */ - public static StrMatcher doubleQuoteMatcher() { - return DOUBLE_QUOTE_MATCHER; - } - - /** - * Returns a matcher which matches the single or double quote character. - * - * @return a matcher for a single or double quote - */ - public static StrMatcher quoteMatcher() { - return QUOTE_MATCHER; - } - - /** - * Matches no characters. - * - * @return a matcher that matches nothing - */ - public static StrMatcher noneMatcher() { - return NONE_MATCHER; - } - - /** - * Constructor that creates a matcher from a character. - * - * @param ch the character to match, must not be null - * @return a new Matcher for the given char - */ - public static StrMatcher charMatcher(final char ch) { - return new CharMatcher(ch); - } - - /** - * Constructor that creates a matcher from a set of characters. - * - * @param chars the characters to match, null or empty matches nothing - * @return a new matcher for the given char[] - */ - public static StrMatcher charSetMatcher(final char... chars) { - if (ArrayUtils.isEmpty(chars)) { - return NONE_MATCHER; + /** + * Constructor that creates a matcher that matches a single character. + * + * @param ch the character to match + */ + CharMatcher(final char ch) { + this.ch = ch; } - if (chars.length == 1) { - return new CharMatcher(chars[0]); + + /** + * Returns whether or not the given character matches. + * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + @Override + public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { + return ch == buffer[pos] ? 1 : 0; } - return new CharSetMatcher(chars); } - - /** - * Constructor that creates a matcher from a string representing a set of characters. - * - * @param chars the characters to match, null or empty matches nothing - * @return a new Matcher for the given characters - */ - public static StrMatcher charSetMatcher(final String chars) { - if (StringUtils.isEmpty(chars)) { - return NONE_MATCHER; - } - if (chars.length() == 1) { - return new CharMatcher(chars.charAt(0)); - } - return new CharSetMatcher(chars.toCharArray()); - } - - /** - * Constructor that creates a matcher from a string. - * - * @param str the string to match, null or empty matches nothing - * @return a new Matcher for the given String - */ - public static StrMatcher stringMatcher(final String str) { - if (StringUtils.isEmpty(str)) { - return NONE_MATCHER; - } - return new StringMatcher(str); - } - - /** - * Constructor. - */ - protected StrMatcher() { - } - - /** - * Returns the number of matching characters, zero for no match. - *

- * This method is called to check for a match. - * The parameter {@code pos} represents the current position to be - * checked in the string {@code buffer} (a character array which must - * not be changed). - * The API guarantees that {@code pos} is a valid index for {@code buffer}. - *

- *

- * The character array may be larger than the active area to be matched. - * Only values in the buffer between the specified indices may be accessed. - *

- *

- * The matching code may check one character or many. - * It may check characters preceding {@code pos} as well as those - * after, so long as no checks exceed the bounds specified. - *

- *

- * It must return zero for no match, or a positive number if a match was found. - * The number indicates the number of characters that matched. - *

- * - * @param buffer the text content to match against, do not change - * @param pos the starting position for the match, valid for buffer - * @param bufferStart the first active index in the buffer, valid for buffer - * @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer - * @return the number of matching characters, zero for no match - */ - public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd); - - /** - * Returns the number of matching characters, zero for no match. - *

- * This method is called to check for a match. - * The parameter {@code pos} represents the current position to be - * checked in the string {@code buffer} (a character array which must - * not be changed). - * The API guarantees that {@code pos} is a valid index for {@code buffer}. - *

- *

- * The matching code may check one character or many. - * It may check characters preceding {@code pos} as well as those after. - *

- *

- * It must return zero for no match, or a positive number if a match was found. - * The number indicates the number of characters that matched. - *

- * - * @param buffer the text content to match against, do not change - * @param pos the starting position for the match, valid for buffer - * @return the number of matching characters, zero for no match - * @since 2.4 - */ - public int isMatch(final char[] buffer, final int pos) { - return isMatch(buffer, pos, 0, buffer.length); - } - /** * Class used to define a set of characters for matching purposes. */ @@ -307,25 +98,19 @@ public abstract class StrMatcher { return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0; } } - /** - * Class used to define a character for matching purposes. + * Class used to match no characters. */ - static final class CharMatcher extends StrMatcher { - /** The character to match. */ - private final char ch; + static final class NoMatcher extends StrMatcher { /** - * Constructor that creates a matcher that matches a single character. - * - * @param ch the character to match + * Constructs a new instance of {@link NoMatcher}. */ - CharMatcher(final char ch) { - this.ch = ch; + NoMatcher() { } /** - * Returns whether or not the given character matches. + * Always returns {@code false}. * * @param buffer the text content to match against, do not change * @param pos the starting position for the match, valid for buffer @@ -335,10 +120,9 @@ public abstract class StrMatcher { */ @Override public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { - return ch == buffer[pos] ? 1 : 0; + return 0; } } - /** * Class used to define a set of characters for matching purposes. */ @@ -384,33 +168,6 @@ public abstract class StrMatcher { } } - - /** - * Class used to match no characters. - */ - static final class NoMatcher extends StrMatcher { - - /** - * Constructs a new instance of {@link NoMatcher}. - */ - NoMatcher() { - } - - /** - * Always returns {@code false}. - * - * @param buffer the text content to match against, do not change - * @param pos the starting position for the match, valid for buffer - * @param bufferStart the first active index in the buffer, valid for buffer - * @param bufferEnd the end index of the active buffer, valid for buffer - * @return the number of matching characters, zero for no match - */ - @Override - public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) { - return 0; - } - } - /** * Class used to match whitespace as per trim(). */ @@ -436,5 +193,248 @@ public abstract class StrMatcher { return buffer[pos] <= 32 ? 1 : 0; } } + /** + * Matches the comma character. + */ + private static final StrMatcher COMMA_MATCHER = new CharMatcher(','); + /** + * Matches the tab character. + */ + private static final StrMatcher TAB_MATCHER = new CharMatcher('\t'); + /** + * Matches the space character. + */ + private static final StrMatcher SPACE_MATCHER = new CharMatcher(' '); + /** + * Matches the same characters as StringTokenizer, + * namely space, tab, newline, formfeed. + */ + private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray()); + + /** + * Matches the String trim() whitespace characters. + */ + private static final StrMatcher TRIM_MATCHER = new TrimMatcher(); + + /** + * Matches the double quote character. + */ + private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\''); + + /** + * Matches the double quote character. + */ + private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"'); + + /** + * Matches the single or double quote character. + */ + private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray()); + + /** + * Matches no characters. + */ + private static final StrMatcher NONE_MATCHER = new NoMatcher(); + + /** + * Constructor that creates a matcher from a character. + * + * @param ch the character to match, must not be null + * @return a new Matcher for the given char + */ + public static StrMatcher charMatcher(final char ch) { + return new CharMatcher(ch); + } + + /** + * Constructor that creates a matcher from a set of characters. + * + * @param chars the characters to match, null or empty matches nothing + * @return a new matcher for the given char[] + */ + public static StrMatcher charSetMatcher(final char... chars) { + if (ArrayUtils.isEmpty(chars)) { + return NONE_MATCHER; + } + if (chars.length == 1) { + return new CharMatcher(chars[0]); + } + return new CharSetMatcher(chars); + } + + /** + * Constructor that creates a matcher from a string representing a set of characters. + * + * @param chars the characters to match, null or empty matches nothing + * @return a new Matcher for the given characters + */ + public static StrMatcher charSetMatcher(final String chars) { + if (StringUtils.isEmpty(chars)) { + return NONE_MATCHER; + } + if (chars.length() == 1) { + return new CharMatcher(chars.charAt(0)); + } + return new CharSetMatcher(chars.toCharArray()); + } + + /** + * Returns a matcher which matches the comma character. + * + * @return a matcher for a comma + */ + public static StrMatcher commaMatcher() { + return COMMA_MATCHER; + } + + /** + * Returns a matcher which matches the double quote character. + * + * @return a matcher for a double quote + */ + public static StrMatcher doubleQuoteMatcher() { + return DOUBLE_QUOTE_MATCHER; + } + + /** + * Matches no characters. + * + * @return a matcher that matches nothing + */ + public static StrMatcher noneMatcher() { + return NONE_MATCHER; + } + + /** + * Returns a matcher which matches the single or double quote character. + * + * @return a matcher for a single or double quote + */ + public static StrMatcher quoteMatcher() { + return QUOTE_MATCHER; + } + + /** + * Returns a matcher which matches the single quote character. + * + * @return a matcher for a single quote + */ + public static StrMatcher singleQuoteMatcher() { + return SINGLE_QUOTE_MATCHER; + } + + /** + * Returns a matcher which matches the space character. + * + * @return a matcher for a space + */ + public static StrMatcher spaceMatcher() { + return SPACE_MATCHER; + } + + /** + * Matches the same characters as StringTokenizer, + * namely space, tab, newline and formfeed. + * + * @return the split matcher + */ + public static StrMatcher splitMatcher() { + return SPLIT_MATCHER; + } + + /** + * Constructor that creates a matcher from a string. + * + * @param str the string to match, null or empty matches nothing + * @return a new Matcher for the given String + */ + public static StrMatcher stringMatcher(final String str) { + if (StringUtils.isEmpty(str)) { + return NONE_MATCHER; + } + return new StringMatcher(str); + } + + /** + * Returns a matcher which matches the tab character. + * + * @return a matcher for a tab + */ + public static StrMatcher tabMatcher() { + return TAB_MATCHER; + } + + /** + * Matches the String trim() whitespace characters. + * + * @return the trim matcher + */ + public static StrMatcher trimMatcher() { + return TRIM_MATCHER; + } + + /** + * Constructor. + */ + protected StrMatcher() { + } + + /** + * Returns the number of matching characters, zero for no match. + *

+ * This method is called to check for a match. + * The parameter {@code pos} represents the current position to be + * checked in the string {@code buffer} (a character array which must + * not be changed). + * The API guarantees that {@code pos} is a valid index for {@code buffer}. + *

+ *

+ * The matching code may check one character or many. + * It may check characters preceding {@code pos} as well as those after. + *

+ *

+ * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + *

+ * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @return the number of matching characters, zero for no match + * @since 2.4 + */ + public int isMatch(final char[] buffer, final int pos) { + return isMatch(buffer, pos, 0, buffer.length); + } + + /** + * Returns the number of matching characters, zero for no match. + *

+ * This method is called to check for a match. + * The parameter {@code pos} represents the current position to be + * checked in the string {@code buffer} (a character array which must + * not be changed). + * The API guarantees that {@code pos} is a valid index for {@code buffer}. + *

+ *

+ * The character array may be larger than the active area to be matched. + * Only values in the buffer between the specified indices may be accessed. + *

+ *

+ * The matching code may check one character or many. + * It may check characters preceding {@code pos} as well as those + * after, so long as no checks exceed the bounds specified. + *

+ *

+ * It must return zero for no match, or a positive number if a match was found. + * The number indicates the number of characters that matched. + *

+ * + * @param buffer the text content to match against, do not change + * @param pos the starting position for the match, valid for buffer + * @param bufferStart the first active index in the buffer, valid for buffer + * @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer + * @return the number of matching characters, zero for no match + */ + public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd); } diff --git a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java index 191ed0d5c..7b97f15a5 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java +++ b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java @@ -166,35 +166,6 @@ public class StrSubstitutor { */ public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-"); - /** - * Stores the escape character. - */ - private char escapeChar; - /** - * Stores the variable prefix. - */ - private StrMatcher prefixMatcher; - /** - * Stores the variable suffix. - */ - private StrMatcher suffixMatcher; - /** - * Stores the default variable value delimiter - */ - private StrMatcher valueDelimiterMatcher; - /** - * Variable resolution is delegated to an implementor of VariableResolver. - */ - private StrLookup variableResolver; - /** - * The flag whether substitution in variable names is enabled. - */ - private boolean enableSubstitutionInVariables; - /** - * Whether escapes should be preserved. Default is false; - */ - private boolean preserveEscapes; - /** * Replaces all the occurrences of variables in the given source object with * their matching values from the map. @@ -207,7 +178,6 @@ public class StrSubstitutor { public static String replace(final Object source, final Map valueMap) { return new StrSubstitutor(valueMap).replace(source); } - /** * Replaces all the occurrences of variables in the given source object with * their matching values from the map. This method allows to specify a @@ -224,7 +194,6 @@ public class StrSubstitutor { public static String replace(final Object source, final Map valueMap, final String prefix, final String suffix) { return new StrSubstitutor(valueMap, prefix, suffix).replace(source); } - /** * Replaces all the occurrences of variables in the given source object with their matching * values from the properties. @@ -246,7 +215,6 @@ public class StrSubstitutor { } return replace(source, valueMap); } - /** * Replaces all the occurrences of variables in the given source object with * their matching values from the system properties. @@ -257,6 +225,38 @@ public class StrSubstitutor { public static String replaceSystemProperties(final Object source) { return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source); } + /** + * Stores the escape character. + */ + private char escapeChar; + /** + * Stores the variable prefix. + */ + private StrMatcher prefixMatcher; + /** + * Stores the variable suffix. + */ + private StrMatcher suffixMatcher; + + /** + * Stores the default variable value delimiter + */ + private StrMatcher valueDelimiterMatcher; + + /** + * Variable resolution is delegated to an implementor of VariableResolver. + */ + private StrLookup variableResolver; + + /** + * The flag whether substitution in variable names is enabled. + */ + private boolean enableSubstitutionInVariables; + + /** + * Whether escapes should be preserved. Default is false; + */ + private boolean preserveEscapes; /** * Creates a new instance with defaults for variable prefix and suffix @@ -406,45 +406,106 @@ public class StrSubstitutor { } /** - * Replaces all the occurrences of variables with their matching values - * from the resolver using the given source string as a template. + * Checks if the specified variable is already in the stack (list) of variables. * - * @param source the string to replace in, null returns null - * @return the result of the replace operation + * @param varName the variable name to check + * @param priorVariables the list of prior variables */ - public String replace(final String source) { - if (source == null) { - return null; + private void checkCyclicSubstitution(final String varName, final List priorVariables) { + if (!priorVariables.contains(varName)) { + return; } - final StrBuilder buf = new StrBuilder(source); - if (!substitute(buf, 0, source.length())) { - return source; - } - return buf.toString(); + final StrBuilder buf = new StrBuilder(256); + buf.append("Infinite loop in property interpolation of "); + buf.append(priorVariables.remove(0)); + buf.append(": "); + buf.appendWithSeparators(priorVariables, "->"); + throw new IllegalStateException(buf.toString()); } /** - * Replaces all the occurrences of variables with their matching values - * from the resolver using the given source string as a template. + * Returns the escape character. + * + * @return the character used for escaping variable references + */ + public char getEscapeChar() { + return this.escapeChar; + } + + /** + * Gets the variable default value delimiter matcher currently in use. *

- * Only the specified portion of the string will be processed. - * The rest of the string is not processed, and is not returned. + * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

+ *

+ * If it returns null, then the variable default value resolution is disabled. *

* - * @param source the string to replace in, null returns null - * @param offset the start offset within the array, must be valid - * @param length the length within the array to be processed, must be valid - * @return the result of the replace operation + * @return the variable default value delimiter matcher in use, may be null + * @since 3.2 */ - public String replace(final String source, final int offset, final int length) { - if (source == null) { - return null; - } - final StrBuilder buf = new StrBuilder(length).append(source, offset, length); - if (!substitute(buf, 0, length)) { - return source.substring(offset, offset + length); - } - return buf.toString(); + public StrMatcher getValueDelimiterMatcher() { + return valueDelimiterMatcher; + } + + /** + * Gets the variable prefix matcher currently in use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + *

+ * + * @return the prefix matcher in use + */ + public StrMatcher getVariablePrefixMatcher() { + return prefixMatcher; + } + + /** + * Gets the VariableResolver that is used to lookup variables. + * + * @return the VariableResolver + */ + public StrLookup getVariableResolver() { + return this.variableResolver; + } + + /** + * Gets the variable suffix matcher currently in use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + *

+ * + * @return the suffix matcher in use + */ + public StrMatcher getVariableSuffixMatcher() { + return suffixMatcher; + } + + /** + * Returns a flag whether substitution is done in variable names. + * + * @return the substitution in variable names flag + * @since 3.0 + */ + public boolean isEnableSubstitutionInVariables() { + return enableSubstitutionInVariables; + } + + /** + * Returns the flag controlling whether escapes are preserved during + * substitution. + * + * @return the preserve escape flag + * @since 3.5 + */ + public boolean isPreserveEscapes() { + return preserveEscapes; } /** @@ -487,46 +548,6 @@ public class StrSubstitutor { return buf.toString(); } - /** - * Replaces all the occurrences of variables with their matching values - * from the resolver using the given source buffer as a template. - * The buffer is not altered by this method. - * - * @param source the buffer to use as a template, not changed, null returns null - * @return the result of the replace operation - */ - public String replace(final StringBuffer source) { - if (source == null) { - return null; - } - final StrBuilder buf = new StrBuilder(source.length()).append(source); - substitute(buf, 0, buf.length()); - return buf.toString(); - } - - /** - * Replaces all the occurrences of variables with their matching values - * from the resolver using the given source buffer as a template. - * The buffer is not altered by this method. - *

- * Only the specified portion of the buffer will be processed. - * The rest of the buffer is not processed, and is not returned. - *

- * - * @param source the buffer to use as a template, not changed, null returns null - * @param offset the start offset within the array, must be valid - * @param length the length within the array to be processed, must be valid - * @return the result of the replace operation - */ - public String replace(final StringBuffer source, final int offset, final int length) { - if (source == null) { - return null; - } - final StrBuilder buf = new StrBuilder(length).append(source, offset, length); - substitute(buf, 0, length); - return buf.toString(); - } - /** * Replaces all the occurrences of variables with their matching values * from the resolver using the given source as a template. @@ -567,6 +588,23 @@ public class StrSubstitutor { return buf.toString(); } + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the resolver. The input source object is + * converted to a string using {@code toString} and is not altered. + * + * @param source the source to replace in, null returns null + * @return the result of the replace operation + */ + public String replace(final Object source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder().append(source); + substitute(buf, 0, buf.length()); + return buf.toString(); + } + /** * Replaces all the occurrences of variables with their matching values * from the resolver using the given source builder as a template. @@ -608,22 +646,121 @@ public class StrSubstitutor { } /** - * Replaces all the occurrences of variables in the given source object with - * their matching values from the resolver. The input source object is - * converted to a string using {@code toString} and is not altered. + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source string as a template. * - * @param source the source to replace in, null returns null + * @param source the string to replace in, null returns null * @return the result of the replace operation */ - public String replace(final Object source) { + public String replace(final String source) { if (source == null) { return null; } - final StrBuilder buf = new StrBuilder().append(source); + final StrBuilder buf = new StrBuilder(source); + if (!substitute(buf, 0, source.length())) { + return source; + } + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source string as a template. + *

+ * Only the specified portion of the string will be processed. + * The rest of the string is not processed, and is not returned. + *

+ * + * @param source the string to replace in, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final String source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (!substitute(buf, 0, length)) { + return source.substring(offset, offset + length); + } + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source buffer as a template. + * The buffer is not altered by this method. + * + * @param source the buffer to use as a template, not changed, null returns null + * @return the result of the replace operation + */ + public String replace(final StringBuffer source) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(source.length()).append(source); substitute(buf, 0, buf.length()); return buf.toString(); } + /** + * Replaces all the occurrences of variables with their matching values + * from the resolver using the given source buffer as a template. + * The buffer is not altered by this method. + *

+ * Only the specified portion of the buffer will be processed. + * The rest of the buffer is not processed, and is not returned. + *

+ * + * @param source the buffer to use as a template, not changed, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(final StringBuffer source, final int offset, final int length) { + if (source == null) { + return null; + } + final StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + * + * @param source the builder to replace in, updated, null returns zero + * @return true if altered + */ + public boolean replaceIn(final StrBuilder source) { + if (source == null) { + return false; + } + return substitute(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + *

+ * Only the specified portion of the builder will be processed. + * The rest of the builder is not processed, but it is not deleted. + *

+ * + * @param source the builder to replace in, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the builder to be processed, must be valid + * @return true if altered + */ + public boolean replaceIn(final StrBuilder source, final int offset, final int length) { + if (source == null) { + return false; + } + return substitute(source, offset, length); + } + /** * Replaces all the occurrences of variables within the given source buffer * with their matching values from the resolver. @@ -709,37 +846,235 @@ public class StrSubstitutor { } /** - * Replaces all the occurrences of variables within the given source - * builder with their matching values from the resolver. + * Internal method that resolves the value of a variable. + *

+ * Most users of this class do not need to call this method. This method is + * called automatically by the substitution process. + *

+ *

+ * Writers of subclasses can override this method if they need to alter + * how each substitution occurs. The method is passed the variable's name + * and must return the corresponding value. This implementation uses the + * {@link #getVariableResolver()} with the variable's name as the key. + *

* - * @param source the builder to replace in, updated, null returns zero - * @return true if altered + * @param variableName the name of the variable, not null + * @param buf the buffer where the substitution is occurring, not null + * @param startPos the start position of the variable including the prefix, valid + * @param endPos the end position of the variable including the suffix, valid + * @return the variable's value or null if the variable is unknown */ - public boolean replaceIn(final StrBuilder source) { - if (source == null) { - return false; + protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) { + final StrLookup resolver = getVariableResolver(); + if (resolver == null) { + return null; } - return substitute(source, 0, source.length()); + return resolver.lookup(variableName); } /** - * Replaces all the occurrences of variables within the given source - * builder with their matching values from the resolver. + * Sets a flag whether substitution is done in variable names. If set to + * true, the names of variables can contain other variables which are + * processed first before the original variable is evaluated, e.g. + * {@code ${jre-${java.version}}}. The default value is false. + * + * @param enableSubstitutionInVariables the new value of the flag + * @since 3.0 + */ + public void setEnableSubstitutionInVariables( + final boolean enableSubstitutionInVariables) { + this.enableSubstitutionInVariables = enableSubstitutionInVariables; + } + + /** + * Sets the escape character. + * If this character is placed before a variable reference in the source + * text, this variable will be ignored. + * + * @param escapeCharacter the escape character (0 for disabling escaping) + */ + public void setEscapeChar(final char escapeCharacter) { + this.escapeChar = escapeCharacter; + } + + /** + * Sets a flag controlling whether escapes are preserved during + * substitution. If set to true, the escape character is retained + * during substitution (e.g. {@code $${this-is-escaped}} remains + * {@code $${this-is-escaped}}). If set to false, the escape + * character is removed during substitution (e.g. + * {@code $${this-is-escaped}} becomes + * {@code ${this-is-escaped}}). The default value is false + * + * @param preserveEscapes true if escapes are to be preserved + * @since 3.5 + */ + public void setPreserveEscapes(final boolean preserveEscapes) { + this.preserveEscapes = preserveEscapes; + } + + /** + * Sets the variable default value delimiter to use. *

- * Only the specified portion of the builder will be processed. - * The rest of the builder is not processed, but it is not deleted. + * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This method allows a single character + * variable default value delimiter to be easily set. *

* - * @param source the builder to replace in, null returns zero - * @param offset the start offset within the array, must be valid - * @param length the length within the builder to be processed, must be valid - * @return true if altered + * @param valueDelimiter the variable default value delimiter character to use + * @return this, to enable chaining + * @since 3.2 */ - public boolean replaceIn(final StrBuilder source, final int offset, final int length) { - if (source == null) { - return false; + public StrSubstitutor setValueDelimiter(final char valueDelimiter) { + return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); + } + + /** + * Sets the variable default value delimiter to use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This method allows a string + * variable default value delimiter to be easily set. + *

+ *

+ * If the {@code valueDelimiter} is null or empty string, then the variable default + * value resolution becomes disabled. + *

+ * + * @param valueDelimiter the variable default value delimiter string to use, may be null or empty + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiter(final String valueDelimiter) { + if (StringUtils.isEmpty(valueDelimiter)) { + setValueDelimiterMatcher(null); + return this; } - return substitute(source, offset, length); + return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); + } + + /** + * Sets the variable default value delimiter matcher to use. + *

+ * The variable default value delimiter is the character or characters that delimit the + * variable name and the variable default value. This delimiter is expressed in terms of a matcher + * allowing advanced variable default value delimiter matches. + *

+ *

+ * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution + * becomes disabled. + *

+ * + * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null + * @return this, to enable chaining + * @since 3.2 + */ + public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { + this.valueDelimiterMatcher = valueDelimiterMatcher; + return this; + } + + /** + * Sets the variable prefix to use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This method allows a single character prefix to + * be easily set. + *

+ * + * @param prefix the prefix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariablePrefix(final char prefix) { + return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); + } + + /** + * Sets the variable prefix to use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This method allows a string prefix to be easily set. + *

+ * + * @param prefix the prefix for variables, not null + * @return this, to enable chaining + * @throws NullPointerException if the prefix is null + */ + public StrSubstitutor setVariablePrefix(final String prefix) { + return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix))); + } + + /** + * Sets the variable prefix matcher currently in use. + *

+ * The variable prefix is the character or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + *

+ * + * @param prefixMatcher the prefix matcher to use, null ignored + * @return this, to enable chaining + * @throws NullPointerException if the prefix matcher is null + */ + public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { + this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher"); + return this; + } + + /** + * Sets the VariableResolver that is used to lookup variables. + * + * @param variableResolver the VariableResolver + */ + public void setVariableResolver(final StrLookup variableResolver) { + this.variableResolver = variableResolver; + } + + /** + * Sets the variable suffix to use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This method allows a single character suffix to + * be easily set. + *

+ * + * @param suffix the suffix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariableSuffix(final char suffix) { + return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); + } + + /** + * Sets the variable suffix to use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This method allows a string suffix to be easily set. + *

+ * + * @param suffix the suffix for variables, not null + * @return this, to enable chaining + * @throws NullPointerException if the suffix is null + */ + public StrSubstitutor setVariableSuffix(final String suffix) { + return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix))); + } + + /** + * Sets the variable suffix matcher currently in use. + *

+ * The variable suffix is the character or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + *

+ * + * @param suffixMatcher the suffix matcher to use, null ignored + * @return this, to enable chaining + * @throws NullPointerException if the suffix matcher is null + */ + public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { + this.suffixMatcher = Objects.requireNonNull(suffixMatcher); + return this; } /** @@ -907,339 +1242,4 @@ public class StrSubstitutor { } return lengthChange; } - - /** - * Checks if the specified variable is already in the stack (list) of variables. - * - * @param varName the variable name to check - * @param priorVariables the list of prior variables - */ - private void checkCyclicSubstitution(final String varName, final List priorVariables) { - if (!priorVariables.contains(varName)) { - return; - } - final StrBuilder buf = new StrBuilder(256); - buf.append("Infinite loop in property interpolation of "); - buf.append(priorVariables.remove(0)); - buf.append(": "); - buf.appendWithSeparators(priorVariables, "->"); - throw new IllegalStateException(buf.toString()); - } - - /** - * Internal method that resolves the value of a variable. - *

- * Most users of this class do not need to call this method. This method is - * called automatically by the substitution process. - *

- *

- * Writers of subclasses can override this method if they need to alter - * how each substitution occurs. The method is passed the variable's name - * and must return the corresponding value. This implementation uses the - * {@link #getVariableResolver()} with the variable's name as the key. - *

- * - * @param variableName the name of the variable, not null - * @param buf the buffer where the substitution is occurring, not null - * @param startPos the start position of the variable including the prefix, valid - * @param endPos the end position of the variable including the suffix, valid - * @return the variable's value or null if the variable is unknown - */ - protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) { - final StrLookup resolver = getVariableResolver(); - if (resolver == null) { - return null; - } - return resolver.lookup(variableName); - } - - /** - * Returns the escape character. - * - * @return the character used for escaping variable references - */ - public char getEscapeChar() { - return this.escapeChar; - } - - /** - * Sets the escape character. - * If this character is placed before a variable reference in the source - * text, this variable will be ignored. - * - * @param escapeCharacter the escape character (0 for disabling escaping) - */ - public void setEscapeChar(final char escapeCharacter) { - this.escapeChar = escapeCharacter; - } - - /** - * Gets the variable prefix matcher currently in use. - *

- * The variable prefix is the character or characters that identify the - * start of a variable. This prefix is expressed in terms of a matcher - * allowing advanced prefix matches. - *

- * - * @return the prefix matcher in use - */ - public StrMatcher getVariablePrefixMatcher() { - return prefixMatcher; - } - - /** - * Sets the variable prefix matcher currently in use. - *

- * The variable prefix is the character or characters that identify the - * start of a variable. This prefix is expressed in terms of a matcher - * allowing advanced prefix matches. - *

- * - * @param prefixMatcher the prefix matcher to use, null ignored - * @return this, to enable chaining - * @throws NullPointerException if the prefix matcher is null - */ - public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { - this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher"); - return this; - } - - /** - * Sets the variable prefix to use. - *

- * The variable prefix is the character or characters that identify the - * start of a variable. This method allows a single character prefix to - * be easily set. - *

- * - * @param prefix the prefix character to use - * @return this, to enable chaining - */ - public StrSubstitutor setVariablePrefix(final char prefix) { - return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); - } - - /** - * Sets the variable prefix to use. - *

- * The variable prefix is the character or characters that identify the - * start of a variable. This method allows a string prefix to be easily set. - *

- * - * @param prefix the prefix for variables, not null - * @return this, to enable chaining - * @throws NullPointerException if the prefix is null - */ - public StrSubstitutor setVariablePrefix(final String prefix) { - return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix))); - } - - /** - * Gets the variable suffix matcher currently in use. - *

- * The variable suffix is the character or characters that identify the - * end of a variable. This suffix is expressed in terms of a matcher - * allowing advanced suffix matches. - *

- * - * @return the suffix matcher in use - */ - public StrMatcher getVariableSuffixMatcher() { - return suffixMatcher; - } - - /** - * Sets the variable suffix matcher currently in use. - *

- * The variable suffix is the character or characters that identify the - * end of a variable. This suffix is expressed in terms of a matcher - * allowing advanced suffix matches. - *

- * - * @param suffixMatcher the suffix matcher to use, null ignored - * @return this, to enable chaining - * @throws NullPointerException if the suffix matcher is null - */ - public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { - this.suffixMatcher = Objects.requireNonNull(suffixMatcher); - return this; - } - - /** - * Sets the variable suffix to use. - *

- * The variable suffix is the character or characters that identify the - * end of a variable. This method allows a single character suffix to - * be easily set. - *

- * - * @param suffix the suffix character to use - * @return this, to enable chaining - */ - public StrSubstitutor setVariableSuffix(final char suffix) { - return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); - } - - /** - * Sets the variable suffix to use. - *

- * The variable suffix is the character or characters that identify the - * end of a variable. This method allows a string suffix to be easily set. - *

- * - * @param suffix the suffix for variables, not null - * @return this, to enable chaining - * @throws NullPointerException if the suffix is null - */ - public StrSubstitutor setVariableSuffix(final String suffix) { - return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix))); - } - - /** - * Gets the variable default value delimiter matcher currently in use. - *

- * The variable default value delimiter is the character or characters that delimit the - * variable name and the variable default value. This delimiter is expressed in terms of a matcher - * allowing advanced variable default value delimiter matches. - *

- *

- * If it returns null, then the variable default value resolution is disabled. - *

- * - * @return the variable default value delimiter matcher in use, may be null - * @since 3.2 - */ - public StrMatcher getValueDelimiterMatcher() { - return valueDelimiterMatcher; - } - - /** - * Sets the variable default value delimiter matcher to use. - *

- * The variable default value delimiter is the character or characters that delimit the - * variable name and the variable default value. This delimiter is expressed in terms of a matcher - * allowing advanced variable default value delimiter matches. - *

- *

- * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution - * becomes disabled. - *

- * - * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null - * @return this, to enable chaining - * @since 3.2 - */ - public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { - this.valueDelimiterMatcher = valueDelimiterMatcher; - return this; - } - - /** - * Sets the variable default value delimiter to use. - *

- * The variable default value delimiter is the character or characters that delimit the - * variable name and the variable default value. This method allows a single character - * variable default value delimiter to be easily set. - *

- * - * @param valueDelimiter the variable default value delimiter character to use - * @return this, to enable chaining - * @since 3.2 - */ - public StrSubstitutor setValueDelimiter(final char valueDelimiter) { - return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); - } - - /** - * Sets the variable default value delimiter to use. - *

- * The variable default value delimiter is the character or characters that delimit the - * variable name and the variable default value. This method allows a string - * variable default value delimiter to be easily set. - *

- *

- * If the {@code valueDelimiter} is null or empty string, then the variable default - * value resolution becomes disabled. - *

- * - * @param valueDelimiter the variable default value delimiter string to use, may be null or empty - * @return this, to enable chaining - * @since 3.2 - */ - public StrSubstitutor setValueDelimiter(final String valueDelimiter) { - if (StringUtils.isEmpty(valueDelimiter)) { - setValueDelimiterMatcher(null); - return this; - } - return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); - } - - /** - * Gets the VariableResolver that is used to lookup variables. - * - * @return the VariableResolver - */ - public StrLookup getVariableResolver() { - return this.variableResolver; - } - - /** - * Sets the VariableResolver that is used to lookup variables. - * - * @param variableResolver the VariableResolver - */ - public void setVariableResolver(final StrLookup variableResolver) { - this.variableResolver = variableResolver; - } - - /** - * Returns a flag whether substitution is done in variable names. - * - * @return the substitution in variable names flag - * @since 3.0 - */ - public boolean isEnableSubstitutionInVariables() { - return enableSubstitutionInVariables; - } - - /** - * Sets a flag whether substitution is done in variable names. If set to - * true, the names of variables can contain other variables which are - * processed first before the original variable is evaluated, e.g. - * {@code ${jre-${java.version}}}. The default value is false. - * - * @param enableSubstitutionInVariables the new value of the flag - * @since 3.0 - */ - public void setEnableSubstitutionInVariables( - final boolean enableSubstitutionInVariables) { - this.enableSubstitutionInVariables = enableSubstitutionInVariables; - } - - /** - * Returns the flag controlling whether escapes are preserved during - * substitution. - * - * @return the preserve escape flag - * @since 3.5 - */ - public boolean isPreserveEscapes() { - return preserveEscapes; - } - - /** - * Sets a flag controlling whether escapes are preserved during - * substitution. If set to true, the escape character is retained - * during substitution (e.g. {@code $${this-is-escaped}} remains - * {@code $${this-is-escaped}}). If set to false, the escape - * character is removed during substitution (e.g. - * {@code $${this-is-escaped}} becomes - * {@code ${this-is-escaped}}). The default value is false - * - * @param preserveEscapes true if escapes are to be preserved - * @since 3.5 - */ - public void setPreserveEscapes(final boolean preserveEscapes) { - this.preserveEscapes = preserveEscapes; - } } diff --git a/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java b/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java index 3711e13de..7c11f1e0f 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java +++ b/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java @@ -116,28 +116,6 @@ public class StrTokenizer implements ListIterator, Cloneable { TSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false); } - /** The text to work on. */ - private char[] chars; - /** The parsed tokens */ - private String[] tokens; - /** The current iteration position */ - private int tokenPos; - - /** The delimiter matcher */ - private StrMatcher delimMatcher = StrMatcher.splitMatcher(); - /** The quote matcher */ - private StrMatcher quoteMatcher = StrMatcher.noneMatcher(); - /** The ignored matcher */ - private StrMatcher ignoredMatcher = StrMatcher.noneMatcher(); - /** The trimmer matcher */ - private StrMatcher trimmerMatcher = StrMatcher.noneMatcher(); - - /** Whether to return empty tokens as null */ - private boolean emptyAsNull; - /** Whether to ignore empty tokens */ - private boolean ignoreEmptyTokens = true; - - /** * Returns a clone of {@code CSV_TOKENIZER_PROTOTYPE}. * @@ -146,7 +124,6 @@ public class StrTokenizer implements ListIterator, Cloneable { private static StrTokenizer getCSVClone() { return (StrTokenizer) CSV_TOKENIZER_PROTOTYPE.clone(); } - /** * Gets a new tokenizer instance which parses Comma Separated Value strings * initializing it with the given input. The default for CSV processing @@ -160,22 +137,6 @@ public class StrTokenizer implements ListIterator, Cloneable { public static StrTokenizer getCSVInstance() { return getCSVClone(); } - - /** - * Gets a new tokenizer instance which parses Comma Separated Value strings - * initializing it with the given input. The default for CSV processing - * will be trim whitespace from both ends (which can be overridden with - * the setTrimmer method). - * - * @param input the text to parse - * @return a new tokenizer instance which parses Comma Separated Value strings - */ - public static StrTokenizer getCSVInstance(final String input) { - final StrTokenizer tok = getCSVClone(); - tok.reset(input); - return tok; - } - /** * Gets a new tokenizer instance which parses Comma Separated Value strings * initializing it with the given input. The default for CSV processing @@ -191,6 +152,20 @@ public class StrTokenizer implements ListIterator, Cloneable { return tok; } + /** + * Gets a new tokenizer instance which parses Comma Separated Value strings + * initializing it with the given input. The default for CSV processing + * will be trim whitespace from both ends (which can be overridden with + * the setTrimmer method). + * + * @param input the text to parse + * @return a new tokenizer instance which parses Comma Separated Value strings + */ + public static StrTokenizer getCSVInstance(final String input) { + final StrTokenizer tok = getCSVClone(); + tok.reset(input); + return tok; + } /** * Returns a clone of {@code TSV_TOKENIZER_PROTOTYPE}. * @@ -199,8 +174,6 @@ public class StrTokenizer implements ListIterator, Cloneable { private static StrTokenizer getTSVClone() { return (StrTokenizer) TSV_TOKENIZER_PROTOTYPE.clone(); } - - /** * Gets a new tokenizer instance which parses Tab Separated Value strings. * The default for CSV processing will be trim whitespace from both ends @@ -213,20 +186,6 @@ public class StrTokenizer implements ListIterator, Cloneable { public static StrTokenizer getTSVInstance() { return getTSVClone(); } - - /** - * Gets a new tokenizer instance which parses Tab Separated Value strings. - * The default for CSV processing will be trim whitespace from both ends - * (which can be overridden with the setTrimmer method). - * @param input the string to parse - * @return a new tokenizer instance which parses Tab Separated Value strings. - */ - public static StrTokenizer getTSVInstance(final String input) { - final StrTokenizer tok = getTSVClone(); - tok.reset(input); - return tok; - } - /** * Gets a new tokenizer instance which parses Tab Separated Value strings. * The default for CSV processing will be trim whitespace from both ends @@ -240,6 +199,47 @@ public class StrTokenizer implements ListIterator, Cloneable { return tok; } + /** + * Gets a new tokenizer instance which parses Tab Separated Value strings. + * The default for CSV processing will be trim whitespace from both ends + * (which can be overridden with the setTrimmer method). + * @param input the string to parse + * @return a new tokenizer instance which parses Tab Separated Value strings. + */ + public static StrTokenizer getTSVInstance(final String input) { + final StrTokenizer tok = getTSVClone(); + tok.reset(input); + return tok; + } + /** The text to work on. */ + private char[] chars; + + + /** The parsed tokens */ + private String[] tokens; + + /** The current iteration position */ + private int tokenPos; + + /** The delimiter matcher */ + private StrMatcher delimMatcher = StrMatcher.splitMatcher(); + + /** The quote matcher */ + private StrMatcher quoteMatcher = StrMatcher.noneMatcher(); + + /** The ignored matcher */ + private StrMatcher ignoredMatcher = StrMatcher.noneMatcher(); + + + /** The trimmer matcher */ + private StrMatcher trimmerMatcher = StrMatcher.noneMatcher(); + + /** Whether to return empty tokens as null */ + private boolean emptyAsNull; + + /** Whether to ignore empty tokens */ + private boolean ignoreEmptyTokens = true; + /** * Constructs a tokenizer splitting on space, tab, newline and formfeed * as per StringTokenizer, but with no text to tokenize. @@ -251,6 +251,75 @@ public class StrTokenizer implements ListIterator, Cloneable { this.chars = null; } + /** + * Constructs a tokenizer splitting on space, tab, newline and formfeed + * as per StringTokenizer. + * + * @param input the string which is to be parsed, not cloned + */ + public StrTokenizer(final char[] input) { + this.chars = ArrayUtils.clone(input); + } + + /** + * Constructs a tokenizer splitting on the specified character. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + */ + public StrTokenizer(final char[] input, final char delim) { + this(input); + setDelimiterChar(delim); + } + + /** + * Constructs a tokenizer splitting on the specified delimiter character + * and handling quotes using the specified quote character. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final char[] input, final char delim, final char quote) { + this(input, delim); + setQuoteChar(quote); + } + + /** + * Constructs a tokenizer splitting on the specified string. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter string + */ + public StrTokenizer(final char[] input, final String delim) { + this(input); + setDelimiterString(delim); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter matcher + */ + public StrTokenizer(final char[] input, final StrMatcher delim) { + this(input); + setDelimiterMatcher(delim); + } + + /** + * Constructs a tokenizer splitting using the specified delimiter matcher + * and handling quotes using the specified quote matcher. + * + * @param input the string which is to be parsed, not cloned + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) { + this(input, delim); + setQuoteMatcher(quote); + } + /** * Constructs a tokenizer splitting on space, tab, newline and formfeed * as per StringTokenizer. @@ -276,6 +345,19 @@ public class StrTokenizer implements ListIterator, Cloneable { setDelimiterChar(delim); } + /** + * Constructs a tokenizer splitting on the specified delimiter character + * and handling quotes using the specified quote character. + * + * @param input the string which is to be parsed + * @param delim the field delimiter character + * @param quote the field quoted string character + */ + public StrTokenizer(final String input, final char delim, final char quote) { + this(input, delim); + setQuoteChar(quote); + } + /** * Constructs a tokenizer splitting on the specified delimiter string. * @@ -298,19 +380,6 @@ public class StrTokenizer implements ListIterator, Cloneable { setDelimiterMatcher(delim); } - /** - * Constructs a tokenizer splitting on the specified delimiter character - * and handling quotes using the specified quote character. - * - * @param input the string which is to be parsed - * @param delim the field delimiter character - * @param quote the field quoted string character - */ - public StrTokenizer(final String input, final char delim, final char quote) { - this(input, delim); - setQuoteChar(quote); - } - /** * Constructs a tokenizer splitting using the specified delimiter matcher * and handling quotes using the specified quote matcher. @@ -325,109 +394,130 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Constructs a tokenizer splitting on space, tab, newline and formfeed - * as per StringTokenizer. - * - * @param input the string which is to be parsed, not cloned + * Unsupported ListIterator operation. + * @param obj this parameter ignored. + * @throws UnsupportedOperationException always */ - public StrTokenizer(final char[] input) { - this.chars = ArrayUtils.clone(input); + @Override + public void add(final String obj) { + throw new UnsupportedOperationException("add() is unsupported"); } /** - * Constructs a tokenizer splitting on the specified character. + * Adds a token to a list, paying attention to the parameters we've set. * - * @param input the string which is to be parsed, not cloned - * @param delim the field delimiter character + * @param list the list to add to + * @param tok the token to add */ - public StrTokenizer(final char[] input, final char delim) { - this(input); - setDelimiterChar(delim); - } - - /** - * Constructs a tokenizer splitting on the specified string. - * - * @param input the string which is to be parsed, not cloned - * @param delim the field delimiter string - */ - public StrTokenizer(final char[] input, final String delim) { - this(input); - setDelimiterString(delim); - } - - /** - * Constructs a tokenizer splitting using the specified delimiter matcher. - * - * @param input the string which is to be parsed, not cloned - * @param delim the field delimiter matcher - */ - public StrTokenizer(final char[] input, final StrMatcher delim) { - this(input); - setDelimiterMatcher(delim); - } - - /** - * Constructs a tokenizer splitting on the specified delimiter character - * and handling quotes using the specified quote character. - * - * @param input the string which is to be parsed, not cloned - * @param delim the field delimiter character - * @param quote the field quoted string character - */ - public StrTokenizer(final char[] input, final char delim, final char quote) { - this(input, delim); - setQuoteChar(quote); - } - - /** - * Constructs a tokenizer splitting using the specified delimiter matcher - * and handling quotes using the specified quote matcher. - * - * @param input the string which is to be parsed, not cloned - * @param delim the field delimiter character - * @param quote the field quoted string character - */ - public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) { - this(input, delim); - setQuoteMatcher(quote); - } - - // API - /** - * Gets the number of tokens found in the String. - * - * @return the number of matched tokens - */ - public int size() { - checkTokenized(); - return tokens.length; - } - - /** - * Gets the next token from the String. - * Equivalent to {@link #next()} except it returns null rather than - * throwing {@link NoSuchElementException} when no tokens remain. - * - * @return the next sequential token, or null when no more tokens are found - */ - public String nextToken() { - if (hasNext()) { - return tokens[tokenPos++]; + private void addToken(final List list, String tok) { + if (StringUtils.isEmpty(tok)) { + if (isIgnoreEmptyTokens()) { + return; + } + if (isEmptyTokenAsNull()) { + tok = null; + } } - return null; + list.add(tok); } /** - * Gets the previous token from the String. - * - * @return the previous sequential token, or null when no more tokens are found + * Checks if tokenization has been done, and if not then do it. */ - public String previousToken() { - if (hasPrevious()) { - return tokens[--tokenPos]; + private void checkTokenized() { + if (tokens == null) { + if (chars == null) { + // still call tokenize as subclass may do some work + final List split = tokenize(null, 0, 0); + tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } else { + final List split = tokenize(chars, 0, chars.length); + tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } } - return null; + } + + /** + * Creates a new instance of this Tokenizer. The new instance is reset so + * that it will be at the start of the token list. + * If a {@link CloneNotSupportedException} is caught, return {@code null}. + * + * @return a new instance of this Tokenizer which has been reset. + */ + @Override + public Object clone() { + try { + return cloneReset(); + } catch (final CloneNotSupportedException ex) { + return null; + } + } + + /** + * Creates a new instance of this Tokenizer. The new instance is reset so that + * it will be at the start of the token list. + * + * @return a new instance of this Tokenizer which has been reset. + * @throws CloneNotSupportedException if there is a problem cloning + */ + Object cloneReset() throws CloneNotSupportedException { + // this method exists to enable 100% test coverage + final StrTokenizer cloned = (StrTokenizer) super.clone(); + if (cloned.chars != null) { + cloned.chars = cloned.chars.clone(); + } + cloned.reset(); + return cloned; + } + + /** + * Gets the String content that the tokenizer is parsing. + * + * @return the string content being parsed + */ + public String getContent() { + if (chars == null) { + return null; + } + return new String(chars); + } + + /** + * Gets the field delimiter matcher. + * + * @return the delimiter matcher in use + */ + public StrMatcher getDelimiterMatcher() { + return this.delimMatcher; + } + + // Ignored + /** + * Gets the ignored character matcher. + *

+ * These characters are ignored when parsing the String, unless they are + * within a quoted region. + * The default value is not to ignore anything. + *

+ * + * @return the ignored matcher in use + */ + public StrMatcher getIgnoredMatcher() { + return ignoredMatcher; + } + + /** + * Gets the quote matcher currently in use. + *

+ * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + * The default value is '"' (double quote). + *

+ * + * @return the quote matcher in use + */ + public StrMatcher getQuoteMatcher() { + return quoteMatcher; } /** @@ -453,49 +543,17 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Resets this tokenizer, forgetting all parsing and iteration already completed. + * Gets the trimmer character matcher. *

- * This method allows the same tokenizer to be reused for the same String. + * These characters are trimmed off on each side of the delimiter + * until the token or quote is found. + * The default value is not to trim anything. *

* - * @return this, to enable chaining + * @return the trimmer matcher in use */ - public StrTokenizer reset() { - tokenPos = 0; - tokens = null; - return this; - } - - /** - * Reset this tokenizer, giving it a new input string to parse. - * In this manner you can re-use a tokenizer with the same settings - * on multiple input lines. - * - * @param input the new string to tokenize, null sets no text to parse - * @return this, to enable chaining - */ - public StrTokenizer reset(final String input) { - reset(); - if (input != null) { - this.chars = input.toCharArray(); - } else { - this.chars = null; - } - return this; - } - - /** - * Reset this tokenizer, giving it a new input string to parse. - * In this manner you can re-use a tokenizer with the same settings - * on multiple input lines. - * - * @param input the new character array to tokenize, not cloned, null sets no text to parse - * @return this, to enable chaining - */ - public StrTokenizer reset(final char[] input) { - reset(); - this.chars = ArrayUtils.clone(input); - return this; + public StrMatcher getTrimmerMatcher() { + return trimmerMatcher; } /** @@ -509,6 +567,57 @@ public class StrTokenizer implements ListIterator, Cloneable { return tokenPos < tokens.length; } + /** + * Checks whether there are any previous tokens that can be iterated to. + * + * @return true if there are previous tokens + */ + @Override + public boolean hasPrevious() { + checkTokenized(); + return tokenPos > 0; + } + + /** + * Gets whether the tokenizer currently returns empty tokens as null. + * The default for this property is false. + * + * @return true if empty tokens are returned as null + */ + public boolean isEmptyTokenAsNull() { + return this.emptyAsNull; + } + + /** + * Gets whether the tokenizer currently ignores empty tokens. + * The default for this property is true. + * + * @return true if empty tokens are not returned + */ + public boolean isIgnoreEmptyTokens() { + return ignoreEmptyTokens; + } + + /** + * Checks if the characters at the index specified match the quote + * already matched in readNextToken(). + * + * @param srcChars the character array being tokenized + * @param pos the position to check for a quote + * @param len the length of the character array being tokenized + * @param quoteStart the start position of the matched quote, 0 if no quoting + * @param quoteLen the length of the matched quote, 0 if no quoting + * @return true if a quote is matched + */ + private boolean isQuote(final char[] srcChars, final int pos, final int len, final int quoteStart, final int quoteLen) { + for (int i = 0; i < quoteLen; i++) { + if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) { + return false; + } + } + return true; + } + /** * Gets the next token. * @@ -534,14 +643,17 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Checks whether there are any previous tokens that can be iterated to. + * Gets the next token from the String. + * Equivalent to {@link #next()} except it returns null rather than + * throwing {@link NoSuchElementException} when no tokens remain. * - * @return true if there are previous tokens + * @return the next sequential token, or null when no more tokens are found */ - @Override - public boolean hasPrevious() { - checkTokenized(); - return tokenPos > 0; + public String nextToken() { + if (hasNext()) { + return tokens[tokenPos++]; + } + return null; } /** @@ -568,111 +680,15 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Unsupported ListIterator operation. + * Gets the previous token from the String. * - * @throws UnsupportedOperationException always + * @return the previous sequential token, or null when no more tokens are found */ - @Override - public void remove() { - throw new UnsupportedOperationException("remove() is unsupported"); - } - - /** - * Unsupported ListIterator operation. - * @param obj this parameter ignored. - * @throws UnsupportedOperationException always - */ - @Override - public void set(final String obj) { - throw new UnsupportedOperationException("set() is unsupported"); - } - - /** - * Unsupported ListIterator operation. - * @param obj this parameter ignored. - * @throws UnsupportedOperationException always - */ - @Override - public void add(final String obj) { - throw new UnsupportedOperationException("add() is unsupported"); - } - - /** - * Checks if tokenization has been done, and if not then do it. - */ - private void checkTokenized() { - if (tokens == null) { - if (chars == null) { - // still call tokenize as subclass may do some work - final List split = tokenize(null, 0, 0); - tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); - } else { - final List split = tokenize(chars, 0, chars.length); - tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY); - } + public String previousToken() { + if (hasPrevious()) { + return tokens[--tokenPos]; } - } - - /** - * Internal method to performs the tokenization. - *

- * Most users of this class do not need to call this method. This method - * will be called automatically by other (public) methods when required. - *

- *

- * This method exists to allow subclasses to add code before or after the - * tokenization. For example, a subclass could alter the character array, - * offset or count to be parsed, or call the tokenizer multiple times on - * multiple strings. It is also be possible to filter the results. - *

- *

- * {@link StrTokenizer} will always pass a zero offset and a count - * equal to the length of the array to this method, however a subclass - * may pass other values, or even an entirely different array. - *

- * - * @param srcChars the character array being tokenized, may be null - * @param offset the start position within the character array, must be valid - * @param count the number of characters to tokenize, must be valid - * @return the modifiable list of String tokens, unmodifiable if null array or zero count - */ - protected List tokenize(final char[] srcChars, final int offset, final int count) { - if (ArrayUtils.isEmpty(srcChars)) { - return Collections.emptyList(); - } - final StrBuilder buf = new StrBuilder(); - final List tokenList = new ArrayList<>(); - int pos = offset; - - // loop around the entire buffer - while (pos >= 0 && pos < count) { - // find next token - pos = readNextToken(srcChars, pos, count, buf, tokenList); - - // handle case where end of string is a delimiter - if (pos >= count) { - addToken(tokenList, StringUtils.EMPTY); - } - } - return tokenList; - } - - /** - * Adds a token to a list, paying attention to the parameters we've set. - * - * @param list the list to add to - * @param tok the token to add - */ - private void addToken(final List list, String tok) { - if (StringUtils.isEmpty(tok)) { - if (isIgnoreEmptyTokens()) { - return; - } - if (isEmptyTokenAsNull()) { - tok = null; - } - } - list.add(tok); + return null; } /** @@ -817,32 +833,79 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Checks if the characters at the index specified match the quote - * already matched in readNextToken(). + * Unsupported ListIterator operation. * - * @param srcChars the character array being tokenized - * @param pos the position to check for a quote - * @param len the length of the character array being tokenized - * @param quoteStart the start position of the matched quote, 0 if no quoting - * @param quoteLen the length of the matched quote, 0 if no quoting - * @return true if a quote is matched + * @throws UnsupportedOperationException always */ - private boolean isQuote(final char[] srcChars, final int pos, final int len, final int quoteStart, final int quoteLen) { - for (int i = 0; i < quoteLen; i++) { - if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) { - return false; - } - } - return true; + @Override + public void remove() { + throw new UnsupportedOperationException("remove() is unsupported"); } /** - * Gets the field delimiter matcher. + * Resets this tokenizer, forgetting all parsing and iteration already completed. + *

+ * This method allows the same tokenizer to be reused for the same String. + *

* - * @return the delimiter matcher in use + * @return this, to enable chaining */ - public StrMatcher getDelimiterMatcher() { - return this.delimMatcher; + public StrTokenizer reset() { + tokenPos = 0; + tokens = null; + return this; + } + + /** + * Reset this tokenizer, giving it a new input string to parse. + * In this manner you can re-use a tokenizer with the same settings + * on multiple input lines. + * + * @param input the new character array to tokenize, not cloned, null sets no text to parse + * @return this, to enable chaining + */ + public StrTokenizer reset(final char[] input) { + reset(); + this.chars = ArrayUtils.clone(input); + return this; + } + + /** + * Reset this tokenizer, giving it a new input string to parse. + * In this manner you can re-use a tokenizer with the same settings + * on multiple input lines. + * + * @param input the new string to tokenize, null sets no text to parse + * @return this, to enable chaining + */ + public StrTokenizer reset(final String input) { + reset(); + if (input != null) { + this.chars = input.toCharArray(); + } else { + this.chars = null; + } + return this; + } + + /** + * Unsupported ListIterator operation. + * @param obj this parameter ignored. + * @throws UnsupportedOperationException always + */ + @Override + public void set(final String obj) { + throw new UnsupportedOperationException("set() is unsupported"); + } + + /** + * Sets the field delimiter character. + * + * @param delim the delimiter character to use + * @return this, to enable chaining + */ + public StrTokenizer setDelimiterChar(final char delim) { + return setDelimiterMatcher(StrMatcher.charMatcher(delim)); } /** @@ -863,16 +926,6 @@ public class StrTokenizer implements ListIterator, Cloneable { return this; } - /** - * Sets the field delimiter character. - * - * @param delim the delimiter character to use - * @return this, to enable chaining - */ - public StrTokenizer setDelimiterChar(final char delim) { - return setDelimiterMatcher(StrMatcher.charMatcher(delim)); - } - /** * Sets the field delimiter string. * @@ -884,63 +937,28 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Gets the quote matcher currently in use. - *

- * The quote character is used to wrap data between the tokens. - * This enables delimiters to be entered as data. - * The default value is '"' (double quote). - *

+ * Sets whether the tokenizer should return empty tokens as null. + * The default for this property is false. * - * @return the quote matcher in use - */ - public StrMatcher getQuoteMatcher() { - return quoteMatcher; - } - - /** - * Sets the quote matcher to use. - *

- * The quote character is used to wrap data between the tokens. - * This enables delimiters to be entered as data. - *

- * - * @param quote the quote matcher to use, null ignored + * @param emptyAsNull whether empty tokens are returned as null * @return this, to enable chaining */ - public StrTokenizer setQuoteMatcher(final StrMatcher quote) { - if (quote != null) { - this.quoteMatcher = quote; - } + public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) { + this.emptyAsNull = emptyAsNull; return this; } /** - * Sets the quote character to use. + * Sets the character to ignore. *

- * The quote character is used to wrap data between the tokens. - * This enables delimiters to be entered as data. - *

+ * This character is ignored when parsing the String, unless it is + * within a quoted region. * - * @param quote the quote character to use + * @param ignored the ignored character to use * @return this, to enable chaining */ - public StrTokenizer setQuoteChar(final char quote) { - return setQuoteMatcher(StrMatcher.charMatcher(quote)); - } - - // Ignored - /** - * Gets the ignored character matcher. - *

- * These characters are ignored when parsing the String, unless they are - * within a quoted region. - * The default value is not to ignore anything. - *

- * - * @return the ignored matcher in use - */ - public StrMatcher getIgnoredMatcher() { - return ignoredMatcher; + public StrTokenizer setIgnoredChar(final char ignored) { + return setIgnoredMatcher(StrMatcher.charMatcher(ignored)); } /** @@ -961,30 +979,46 @@ public class StrTokenizer implements ListIterator, Cloneable { } /** - * Sets the character to ignore. - *

- * This character is ignored when parsing the String, unless it is - * within a quoted region. + * Sets whether the tokenizer should ignore and not return empty tokens. + * The default for this property is true. * - * @param ignored the ignored character to use + * @param ignoreEmptyTokens whether empty tokens are not returned * @return this, to enable chaining */ - public StrTokenizer setIgnoredChar(final char ignored) { - return setIgnoredMatcher(StrMatcher.charMatcher(ignored)); + public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) { + this.ignoreEmptyTokens = ignoreEmptyTokens; + return this; } /** - * Gets the trimmer character matcher. + * Sets the quote character to use. *

- * These characters are trimmed off on each side of the delimiter - * until the token or quote is found. - * The default value is not to trim anything. + * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. *

* - * @return the trimmer matcher in use + * @param quote the quote character to use + * @return this, to enable chaining */ - public StrMatcher getTrimmerMatcher() { - return trimmerMatcher; + public StrTokenizer setQuoteChar(final char quote) { + return setQuoteMatcher(StrMatcher.charMatcher(quote)); + } + + /** + * Sets the quote matcher to use. + *

+ * The quote character is used to wrap data between the tokens. + * This enables delimiters to be entered as data. + *

+ * + * @param quote the quote matcher to use, null ignored + * @return this, to enable chaining + */ + public StrTokenizer setQuoteMatcher(final StrMatcher quote) { + if (quote != null) { + this.quoteMatcher = quote; + } + return this; } /** @@ -1004,93 +1038,59 @@ public class StrTokenizer implements ListIterator, Cloneable { return this; } + // API /** - * Gets whether the tokenizer currently returns empty tokens as null. - * The default for this property is false. + * Gets the number of tokens found in the String. * - * @return true if empty tokens are returned as null + * @return the number of matched tokens */ - public boolean isEmptyTokenAsNull() { - return this.emptyAsNull; + public int size() { + checkTokenized(); + return tokens.length; } /** - * Sets whether the tokenizer should return empty tokens as null. - * The default for this property is false. + * Internal method to performs the tokenization. + *

+ * Most users of this class do not need to call this method. This method + * will be called automatically by other (public) methods when required. + *

+ *

+ * This method exists to allow subclasses to add code before or after the + * tokenization. For example, a subclass could alter the character array, + * offset or count to be parsed, or call the tokenizer multiple times on + * multiple strings. It is also be possible to filter the results. + *

+ *

+ * {@link StrTokenizer} will always pass a zero offset and a count + * equal to the length of the array to this method, however a subclass + * may pass other values, or even an entirely different array. + *

* - * @param emptyAsNull whether empty tokens are returned as null - * @return this, to enable chaining + * @param srcChars the character array being tokenized, may be null + * @param offset the start position within the character array, must be valid + * @param count the number of characters to tokenize, must be valid + * @return the modifiable list of String tokens, unmodifiable if null array or zero count */ - public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) { - this.emptyAsNull = emptyAsNull; - return this; - } - - /** - * Gets whether the tokenizer currently ignores empty tokens. - * The default for this property is true. - * - * @return true if empty tokens are not returned - */ - public boolean isIgnoreEmptyTokens() { - return ignoreEmptyTokens; - } - - /** - * Sets whether the tokenizer should ignore and not return empty tokens. - * The default for this property is true. - * - * @param ignoreEmptyTokens whether empty tokens are not returned - * @return this, to enable chaining - */ - public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) { - this.ignoreEmptyTokens = ignoreEmptyTokens; - return this; - } - - /** - * Gets the String content that the tokenizer is parsing. - * - * @return the string content being parsed - */ - public String getContent() { - if (chars == null) { - return null; + protected List tokenize(final char[] srcChars, final int offset, final int count) { + if (ArrayUtils.isEmpty(srcChars)) { + return Collections.emptyList(); } - return new String(chars); - } + final StrBuilder buf = new StrBuilder(); + final List tokenList = new ArrayList<>(); + int pos = offset; - /** - * Creates a new instance of this Tokenizer. The new instance is reset so - * that it will be at the start of the token list. - * If a {@link CloneNotSupportedException} is caught, return {@code null}. - * - * @return a new instance of this Tokenizer which has been reset. - */ - @Override - public Object clone() { - try { - return cloneReset(); - } catch (final CloneNotSupportedException ex) { - return null; - } - } + // loop around the entire buffer + while (pos >= 0 && pos < count) { + // find next token + pos = readNextToken(srcChars, pos, count, buf, tokenList); - /** - * Creates a new instance of this Tokenizer. The new instance is reset so that - * it will be at the start of the token list. - * - * @return a new instance of this Tokenizer which has been reset. - * @throws CloneNotSupportedException if there is a problem cloning - */ - Object cloneReset() throws CloneNotSupportedException { - // this method exists to enable 100% test coverage - final StrTokenizer cloned = (StrTokenizer) super.clone(); - if (cloned.chars != null) { - cloned.chars = cloned.chars.clone(); + // handle case where end of string is a delimiter + if (pos >= count) { + addToken(tokenList, StringUtils.EMPTY); + } } - cloned.reset(); - return cloned; + return tokenList; } /** diff --git a/src/main/java/org/apache/commons/lang3/text/WordUtils.java b/src/main/java/org/apache/commons/lang3/text/WordUtils.java index d5ca6eaa9..5dc5ea258 100644 --- a/src/main/java/org/apache/commons/lang3/text/WordUtils.java +++ b/src/main/java/org/apache/commons/lang3/text/WordUtils.java @@ -37,15 +37,375 @@ import org.apache.commons.lang3.StringUtils; @Deprecated public class WordUtils { + // Capitalizing /** - * {@link WordUtils} instances should NOT be constructed in - * standard programming. Instead, the class should be used as - * {@code WordUtils.wrap("foo bar", 20);}. + * Capitalizes all the whitespace separated words in a String. + * Only the first character of each word is changed. To convert the + * rest of each word to lowercase at the same time, + * use {@link #capitalizeFully(String)}. * - *

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

+ *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code 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, {@code null} if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) */ - public WordUtils() { + public static String capitalize(final String str) { + return capitalize(str, null); + } + + /** + * Capitalizes all the delimiter separated words in a String. + * Only the first character 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 {@code null} input String returns {@code 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, {@code null} if null String input + * @see #uncapitalize(String) + * @see #capitalizeFully(String) + * @since 2.1 + */ + public static String capitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final 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 {@code null} input String returns {@code 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, {@code null} if null String input + */ + public static String capitalizeFully(final 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 {@code null} input String returns {@code 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, {@code null} if null String input + * @since 2.1 + */ + public static String capitalizeFully(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + return capitalize(str.toLowerCase(), delimiters); + } + + /** + * Checks if the String contains all words in the given array. + * + *

+ * A {@code null} String will return {@code false}. A {@code null}, zero + * length search array or if one element of array is null will return {@code false}. + *

+ * + *
+     * WordUtils.containsAllWords(null, *)            = false
+     * WordUtils.containsAllWords("", *)              = false
+     * WordUtils.containsAllWords(*, null)            = false
+     * WordUtils.containsAllWords(*, [])              = false
+     * WordUtils.containsAllWords("abcd", "ab", "cd") = false
+     * WordUtils.containsAllWords("abc def", "def", "abc") = true
+     * 
+ * + * @param word The CharSequence to check, may be null + * @param words The array of String words to search for, may be null + * @return {@code true} if all search words are found, {@code false} otherwise + * @since 3.5 + */ + public static boolean containsAllWords(final CharSequence word, final CharSequence... words) { + if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) { + return false; + } + for (final CharSequence w : words) { + if (StringUtils.isBlank(w)) { + return false; + } + final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*"); + if (!p.matcher(word).matches()) { + return false; + } + } + return true; + } + + /** + * Extracts the initial characters from each word in the String. + * + *

All first characters after whitespace are returned as a new string. + * Their case is not changed.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code 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, {@code null} if null String input + * @see #initials(String,char[]) + * @since 2.2 + */ + public static String initials(final String str) { + return initials(str, null); + } + + /** + * Extracts the initial characters from each word in the String. + * + *

All first characters 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 {@code null} input String returns {@code 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 characters, {@code null} if null String input + * @see #initials(String) + * @since 2.2 + */ + public static String initials(final String str, final char... delimiters) { + if (StringUtils.isEmpty(str)) { + return str; + } + if (delimiters != null && delimiters.length == 0) { + return StringUtils.EMPTY; + } + final int strLen = str.length(); + final char[] buf = new char[strLen / 2 + 1]; + int count = 0; + boolean lastWasGap = true; + for (int i = 0; i < strLen; i++) { + final 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); + } + + /** + * Tests if the character is a delimiter. + * + * @param ch the character to check + * @param delimiters the delimiters + * @return true if it is a delimiter + */ + private static boolean isDelimiter(final char ch, final char[] delimiters) { + return delimiters == null ? Character.isWhitespace(ch) : ArrayUtils.contains(delimiters, ch); + } + + /** + * 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 {@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"
+     * 
+ * + * @param str the String to swap case, may be null + * @return the changed String, {@code null} if null String input + */ + public static String swapCase(final String str) { + if (StringUtils.isEmpty(str)) { + return str; + } + final char[] buffer = str.toCharArray(); + + boolean whitespace = true; + + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (Character.isUpperCase(ch) || 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); + } + + /** + * Uncapitalizes all the whitespace separated words in a String. + * Only the first character of each word is changed. + * + *

Whitespace is defined by {@link Character#isWhitespace(char)}. + * A {@code null} input String returns {@code 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, {@code null} if null String input + * @see #capitalize(String) + */ + public static String uncapitalize(final String str) { + return uncapitalize(str, null); + } + + /** + * Uncapitalizes all the whitespace separated words in a String. + * Only the first character 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 {@code null} input String returns {@code 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, {@code null} if null String input + * @see #capitalize(String) + * @since 2.1 + */ + public static String uncapitalize(final String str, final char... delimiters) { + final int delimLen = delimiters == null ? -1 : delimiters.length; + if (StringUtils.isEmpty(str) || delimLen == 0) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean uncapitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (isDelimiter(ch, delimiters)) { + uncapitalizeNext = true; + } else if (uncapitalizeNext) { + buffer[i] = Character.toLowerCase(ch); + uncapitalizeNext = false; + } + } + return new String(buffer); } /** @@ -343,375 +703,15 @@ public class WordUtils { return wrappedLine.toString(); } - // Capitalizing /** - * Capitalizes all the whitespace separated words in a String. - * Only the first character of each word is changed. To convert the - * rest of each word to lowercase at the same time, - * use {@link #capitalizeFully(String)}. + * {@link WordUtils} instances should NOT be constructed in + * standard programming. Instead, the class should be used as + * {@code WordUtils.wrap("foo bar", 20);}. * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A {@code null} input String returns {@code 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, {@code null} if null String input - * @see #uncapitalize(String) - * @see #capitalizeFully(String) + *

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

*/ - public static String capitalize(final String str) { - return capitalize(str, null); - } - - /** - * Capitalizes all the delimiter separated words in a String. - * Only the first character 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 {@code null} input String returns {@code 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, {@code null} if null String input - * @see #uncapitalize(String) - * @see #capitalizeFully(String) - * @since 2.1 - */ - public static String capitalize(final String str, final char... delimiters) { - final int delimLen = delimiters == null ? -1 : delimiters.length; - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - final char[] buffer = str.toCharArray(); - boolean capitalizeNext = true; - for (int i = 0; i < buffer.length; i++) { - final 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 {@code null} input String returns {@code 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, {@code null} if null String input - */ - public static String capitalizeFully(final 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 {@code null} input String returns {@code 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, {@code null} if null String input - * @since 2.1 - */ - public static String capitalizeFully(final String str, final char... delimiters) { - final int delimLen = delimiters == null ? -1 : delimiters.length; - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - return capitalize(str.toLowerCase(), delimiters); - } - - /** - * Uncapitalizes all the whitespace separated words in a String. - * Only the first character of each word is changed. - * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A {@code null} input String returns {@code 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, {@code null} if null String input - * @see #capitalize(String) - */ - public static String uncapitalize(final String str) { - return uncapitalize(str, null); - } - - /** - * Uncapitalizes all the whitespace separated words in a String. - * Only the first character 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 {@code null} input String returns {@code 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, {@code null} if null String input - * @see #capitalize(String) - * @since 2.1 - */ - public static String uncapitalize(final String str, final char... delimiters) { - final int delimLen = delimiters == null ? -1 : delimiters.length; - if (StringUtils.isEmpty(str) || delimLen == 0) { - return str; - } - final char[] buffer = str.toCharArray(); - boolean uncapitalizeNext = true; - for (int i = 0; i < buffer.length; i++) { - final 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 {@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"
-     * 
- * - * @param str the String to swap case, may be null - * @return the changed String, {@code null} if null String input - */ - public static String swapCase(final String str) { - if (StringUtils.isEmpty(str)) { - return str; - } - final char[] buffer = str.toCharArray(); - - boolean whitespace = true; - - for (int i = 0; i < buffer.length; i++) { - final char ch = buffer[i]; - if (Character.isUpperCase(ch) || 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 characters from each word in the String. - * - *

All first characters after whitespace are returned as a new string. - * Their case is not changed.

- * - *

Whitespace is defined by {@link Character#isWhitespace(char)}. - * A {@code null} input String returns {@code 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, {@code null} if null String input - * @see #initials(String,char[]) - * @since 2.2 - */ - public static String initials(final String str) { - return initials(str, null); - } - - /** - * Extracts the initial characters from each word in the String. - * - *

All first characters 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 {@code null} input String returns {@code 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 characters, {@code null} if null String input - * @see #initials(String) - * @since 2.2 - */ - public static String initials(final String str, final char... delimiters) { - if (StringUtils.isEmpty(str)) { - return str; - } - if (delimiters != null && delimiters.length == 0) { - return StringUtils.EMPTY; - } - final int strLen = str.length(); - final char[] buf = new char[strLen / 2 + 1]; - int count = 0; - boolean lastWasGap = true; - for (int i = 0; i < strLen; i++) { - final 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); - } - - /** - * Checks if the String contains all words in the given array. - * - *

- * A {@code null} String will return {@code false}. A {@code null}, zero - * length search array or if one element of array is null will return {@code false}. - *

- * - *
-     * WordUtils.containsAllWords(null, *)            = false
-     * WordUtils.containsAllWords("", *)              = false
-     * WordUtils.containsAllWords(*, null)            = false
-     * WordUtils.containsAllWords(*, [])              = false
-     * WordUtils.containsAllWords("abcd", "ab", "cd") = false
-     * WordUtils.containsAllWords("abc def", "def", "abc") = true
-     * 
- * - * @param word The CharSequence to check, may be null - * @param words The array of String words to search for, may be null - * @return {@code true} if all search words are found, {@code false} otherwise - * @since 3.5 - */ - public static boolean containsAllWords(final CharSequence word, final CharSequence... words) { - if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) { - return false; - } - for (final CharSequence w : words) { - if (StringUtils.isBlank(w)) { - return false; - } - final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*"); - if (!p.matcher(word).matches()) { - return false; - } - } - return true; - } - - /** - * Tests if the character is a delimiter. - * - * @param ch the character to check - * @param delimiters the delimiters - * @return true if it is a delimiter - */ - private static boolean isDelimiter(final char ch, final char[] delimiters) { - return delimiters == null ? Character.isWhitespace(ch) : ArrayUtils.contains(delimiters, ch); + public WordUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java b/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java index afbb91960..f9fd33b6d 100644 --- a/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java +++ b/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java @@ -39,18 +39,15 @@ public abstract class CharSequenceTranslator { static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; /** - * Translate a set of code points, represented by an int index into a CharSequence, - * into another set of code points. The number of code points consumed must be returned, - * and the only IOExceptions thrown must be from interacting with the Writer so that - * the top level API may reliably ignore StringWriter IOExceptions. + * Returns an upper case hexadecimal {@link String} for the given + * character. * - * @param input CharSequence that is being translated - * @param index int representing the current point of translation - * @param out Writer to translate the text to - * @return int count of code points consumed - * @throws IOException if and only if the Writer produces an IOException + * @param codePoint The code point to convert. + * @return An upper case hexadecimal {@link String} */ - public abstract int translate(CharSequence input, int index, Writer out) throws IOException; + public static String hex(final int codePoint) { + return Integer.toHexString(codePoint).toUpperCase(Locale.ENGLISH); + } /** * Helper for non-Writer usage. @@ -71,6 +68,20 @@ public abstract class CharSequenceTranslator { } } + /** + * Translate a set of code points, represented by an int index into a CharSequence, + * into another set of code points. The number of code points consumed must be returned, + * and the only IOExceptions thrown must be from interacting with the Writer so that + * the top level API may reliably ignore StringWriter IOExceptions. + * + * @param input CharSequence that is being translated + * @param index int representing the current point of translation + * @param out Writer to translate the text to + * @return int count of code points consumed + * @throws IOException if and only if the Writer produces an IOException + */ + public abstract int translate(CharSequence input, int index, Writer out) throws IOException; + /** * Translate an input onto a Writer. This is intentionally final as its algorithm is * tightly coupled with the abstract method of this class. @@ -125,15 +136,4 @@ public abstract class CharSequenceTranslator { return new AggregateTranslator(newArray); } - /** - * Returns an upper case hexadecimal {@link String} for the given - * character. - * - * @param codePoint The code point to convert. - * @return An upper case hexadecimal {@link String} - */ - public static String hex(final int codePoint) { - return Integer.toHexString(codePoint).toUpperCase(Locale.ENGLISH); - } - } diff --git a/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java b/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java index bfcdf94b2..a157d67ea 100644 --- a/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java +++ b/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java @@ -29,15 +29,6 @@ package org.apache.commons.lang3.text.translate; @Deprecated public class EntityArrays { - /** - * Mapping to escape
ISO-8859-1 - * characters to their named HTML 3.x equivalents. - * @return the mapping table - */ - public static String[][] ISO8859_1_ESCAPE() { - return ISO8859_1_ESCAPE.clone(); - } - private static final String[][] ISO8859_1_ESCAPE = { {"\u00A0", " "}, // non-breaking space {"\u00A1", "¡"}, // inverted exclamation mark @@ -137,26 +128,8 @@ public class EntityArrays { {"\u00FF", "ÿ"}, // ÿ - lowercase y, umlaut }; - /** - * Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes. - * @return the mapping table - */ - public static String[][] ISO8859_1_UNESCAPE() { - return ISO8859_1_UNESCAPE.clone(); - } - private static final String[][] ISO8859_1_UNESCAPE = invert(ISO8859_1_ESCAPE); - /** - * Mapping to escape additional character entity - * references. Note that this must be used with {@link #ISO8859_1_ESCAPE()} to get the full list of - * HTML 4.0 character entities. - * @return the mapping table - */ - public static String[][] HTML40_EXTENDED_ESCAPE() { - return HTML40_EXTENDED_ESCAPE.clone(); - } - private static final String[][] HTML40_EXTENDED_ESCAPE = { // {"\u0192", "ƒ"}, // latin small f with hook = function= florin, U+0192 ISOtech --> @@ -354,15 +327,48 @@ public class EntityArrays { {"\u20AC", "€"}, // -- euro sign, U+20AC NEW --> }; + private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE); + + private static final String[][] BASIC_ESCAPE = { + {"\"", """}, // " - double-quote + {"&", "&"}, // & - ampersand + {"<", "<"}, // < - less-than + {">", ">"}, // > - greater-than + }; + + private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE); + + private static final String[][] APOS_ESCAPE = { + {"'", "'"}, // XML apostrophe + }; + + private static final String[][] APOS_UNESCAPE = invert(APOS_ESCAPE); + + private static final String[][] JAVA_CTRL_CHARS_ESCAPE = { + {"\b", "\\b"}, + {"\n", "\\n"}, + {"\t", "\\t"}, + {"\f", "\\f"}, + {"\r", "\\r"} + }; + + private static final String[][] JAVA_CTRL_CHARS_UNESCAPE = invert(JAVA_CTRL_CHARS_ESCAPE); + /** - * Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes. + * Mapping to escape the apostrophe character to its XML character entity. * @return the mapping table */ - public static String[][] HTML40_EXTENDED_UNESCAPE() { - return HTML40_EXTENDED_UNESCAPE.clone(); + public static String[][] APOS_ESCAPE() { + return APOS_ESCAPE.clone(); } - private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE); + /** + * Reverse of {@link #APOS_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] APOS_UNESCAPE() { + return APOS_UNESCAPE.clone(); + } /** * Mapping to escape the basic XML and HTML character entities. @@ -374,13 +380,6 @@ public class EntityArrays { return BASIC_ESCAPE.clone(); } - private static final String[][] BASIC_ESCAPE = { - {"\"", """}, // " - double-quote - {"&", "&"}, // & - ampersand - {"<", "<"}, // < - less-than - {">", ">"}, // > - greater-than - }; - /** * Reverse of {@link #BASIC_ESCAPE()} for unescaping purposes. * @return the mapping table @@ -389,58 +388,24 @@ public class EntityArrays { return BASIC_UNESCAPE.clone(); } - private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE); - /** - * Mapping to escape the apostrophe character to its XML character entity. + * Mapping to escape additional character entity + * references. Note that this must be used with {@link #ISO8859_1_ESCAPE()} to get the full list of + * HTML 4.0 character entities. * @return the mapping table */ - public static String[][] APOS_ESCAPE() { - return APOS_ESCAPE.clone(); + public static String[][] HTML40_EXTENDED_ESCAPE() { + return HTML40_EXTENDED_ESCAPE.clone(); } - private static final String[][] APOS_ESCAPE = { - {"'", "'"}, // XML apostrophe - }; - /** - * Reverse of {@link #APOS_ESCAPE()} for unescaping purposes. + * Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes. * @return the mapping table */ - public static String[][] APOS_UNESCAPE() { - return APOS_UNESCAPE.clone(); + public static String[][] HTML40_EXTENDED_UNESCAPE() { + return HTML40_EXTENDED_UNESCAPE.clone(); } - private static final String[][] APOS_UNESCAPE = invert(APOS_ESCAPE); - - /** - * Mapping to escape the Java control characters. - * - * Namely: {@code \b \n \t \f \r} - * @return the mapping table - */ - public static String[][] JAVA_CTRL_CHARS_ESCAPE() { - return JAVA_CTRL_CHARS_ESCAPE.clone(); - } - - private static final String[][] JAVA_CTRL_CHARS_ESCAPE = { - {"\b", "\\b"}, - {"\n", "\\n"}, - {"\t", "\\t"}, - {"\f", "\\f"}, - {"\r", "\\r"} - }; - - /** - * Reverse of {@link #JAVA_CTRL_CHARS_ESCAPE()} for unescaping purposes. - * @return the mapping table - */ - public static String[][] JAVA_CTRL_CHARS_UNESCAPE() { - return JAVA_CTRL_CHARS_UNESCAPE.clone(); - } - - private static final String[][] JAVA_CTRL_CHARS_UNESCAPE = invert(JAVA_CTRL_CHARS_ESCAPE); - /** * Used to invert an escape array into an unescape array * @param array String[][] to be inverted @@ -455,4 +420,39 @@ public class EntityArrays { return newarray; } + /** + * Mapping to escape ISO-8859-1 + * characters to their named HTML 3.x equivalents. + * @return the mapping table + */ + public static String[][] ISO8859_1_ESCAPE() { + return ISO8859_1_ESCAPE.clone(); + } + + /** + * Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] ISO8859_1_UNESCAPE() { + return ISO8859_1_UNESCAPE.clone(); + } + + /** + * Mapping to escape the Java control characters. + * + * Namely: {@code \b \n \t \f \r} + * @return the mapping table + */ + public static String[][] JAVA_CTRL_CHARS_ESCAPE() { + return JAVA_CTRL_CHARS_ESCAPE.clone(); + } + + /** + * Reverse of {@link #JAVA_CTRL_CHARS_ESCAPE()} for unescaping purposes. + * @return the mapping table + */ + public static String[][] JAVA_CTRL_CHARS_UNESCAPE() { + return JAVA_CTRL_CHARS_UNESCAPE.clone(); + } + } diff --git a/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java b/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java index 538c7006b..9b77fc5da 100644 --- a/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java +++ b/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java @@ -30,43 +30,6 @@ import java.io.Writer; @Deprecated public class NumericEntityEscaper extends CodePointTranslator { - private final int below; - private final int above; - private final boolean between; - - /** - * Constructs a {@link NumericEntityEscaper} for the specified range. This is - * the underlying method for the other constructors/builders. The {@code below} - * and {@code above} boundaries are inclusive when {@code between} is - * {@code true} and exclusive when it is {@code false}. - * - * @param below int value representing the lowest code point boundary - * @param above int value representing the highest code point boundary - * @param between whether to escape between the boundaries or outside them - */ - private NumericEntityEscaper(final int below, final int above, final boolean between) { - this.below = below; - this.above = above; - this.between = between; - } - - /** - * Constructs a {@link NumericEntityEscaper} for all characters. - */ - public NumericEntityEscaper() { - this(0, Integer.MAX_VALUE, true); - } - - /** - * Constructs a {@link NumericEntityEscaper} below the specified value (exclusive). - * - * @param codePoint below which to escape - * @return the newly created {@link NumericEntityEscaper} instance - */ - public static NumericEntityEscaper below(final int codePoint) { - return outsideOf(codePoint, Integer.MAX_VALUE); - } - /** * Constructs a {@link NumericEntityEscaper} above the specified value (exclusive). * @@ -76,7 +39,15 @@ public class NumericEntityEscaper extends CodePointTranslator { public static NumericEntityEscaper above(final int codePoint) { return outsideOf(0, codePoint); } - + /** + * Constructs a {@link NumericEntityEscaper} below the specified value (exclusive). + * + * @param codePoint below which to escape + * @return the newly created {@link NumericEntityEscaper} instance + */ + public static NumericEntityEscaper below(final int codePoint) { + return outsideOf(codePoint, Integer.MAX_VALUE); + } /** * Constructs a {@link NumericEntityEscaper} between the specified values (inclusive). * @@ -99,6 +70,35 @@ public class NumericEntityEscaper extends CodePointTranslator { return new NumericEntityEscaper(codePointLow, codePointHigh, false); } + private final int below; + + private final int above; + + private final boolean between; + + /** + * Constructs a {@link NumericEntityEscaper} for all characters. + */ + public NumericEntityEscaper() { + this(0, Integer.MAX_VALUE, true); + } + + /** + * Constructs a {@link NumericEntityEscaper} for the specified range. This is + * the underlying method for the other constructors/builders. The {@code below} + * and {@code above} boundaries are inclusive when {@code between} is + * {@code true} and exclusive when it is {@code false}. + * + * @param below int value representing the lowest code point boundary + * @param above int value representing the highest code point boundary + * @param between whether to escape between the boundaries or outside them + */ + private NumericEntityEscaper(final int below, final int above, final boolean between) { + this.below = below; + this.above = above; + this.between = between; + } + /** * {@inheritDoc} */ diff --git a/src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java b/src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java index 7f37e449f..8ffee1ff0 100644 --- a/src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java +++ b/src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java @@ -35,6 +35,24 @@ import java.io.Writer; @Deprecated public class OctalUnescaper extends CharSequenceTranslator { + /** + * Checks if the given char is an octal digit. Octal digits are the character representations of the digits 0 to 7. + * @param ch the char to check + * @return true if the given char is the character representation of one of the digits from 0 to 7 + */ + private boolean isOctalDigit(final char ch) { + return ch >= '0' && ch <= '7'; + } + + /** + * Checks if the given char is the character representation of one of the digit from 0 to 3. + * @param ch the char to check + * @return true if the given char is the character representation of one of the digits from 0 to 3 + */ + private boolean isZeroToThree(final char ch) { + return ch >= '0' && ch <= '3'; + } + /** * {@inheritDoc} */ @@ -62,22 +80,4 @@ public class OctalUnescaper extends CharSequenceTranslator { } return 0; } - - /** - * Checks if the given char is an octal digit. Octal digits are the character representations of the digits 0 to 7. - * @param ch the char to check - * @return true if the given char is the character representation of one of the digits from 0 to 7 - */ - private boolean isOctalDigit(final char ch) { - return ch >= '0' && ch <= '7'; - } - - /** - * Checks if the given char is the character representation of one of the digit from 0 to 3. - * @param ch the char to check - * @return true if the given char is the character representation of one of the digits from 0 to 3 - */ - private boolean isZeroToThree(final char ch) { - return ch >= '0' && ch <= '3'; - } } diff --git a/src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java index 3f0b0dce7..25d76c005 100644 --- a/src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java +++ b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java @@ -30,8 +30,50 @@ import java.io.Writer; @Deprecated public class UnicodeEscaper extends CodePointTranslator { + /** + * Constructs a {@link UnicodeEscaper} above the specified value (exclusive). + * + * @param codePoint above which to escape + * @return the newly created {@link UnicodeEscaper} instance + */ + public static UnicodeEscaper above(final int codePoint) { + return outsideOf(0, codePoint); + } + /** + * Constructs a {@link UnicodeEscaper} below the specified value (exclusive). + * + * @param codePoint below which to escape + * @return the newly created {@link UnicodeEscaper} instance + */ + public static UnicodeEscaper below(final int codePoint) { + return outsideOf(codePoint, Integer.MAX_VALUE); + } + /** + * Constructs a {@link UnicodeEscaper} between the specified values (inclusive). + * + * @param codePointLow above which to escape + * @param codePointHigh below which to escape + * @return the newly created {@link UnicodeEscaper} instance + */ + public static UnicodeEscaper between(final int codePointLow, final int codePointHigh) { + return new UnicodeEscaper(codePointLow, codePointHigh, true); + } + + /** + * Constructs a {@link UnicodeEscaper} outside of the specified values (exclusive). + * + * @param codePointLow below which to escape + * @param codePointHigh above which to escape + * @return the newly created {@link UnicodeEscaper} instance + */ + public static UnicodeEscaper outsideOf(final int codePointLow, final int codePointHigh) { + return new UnicodeEscaper(codePointLow, codePointHigh, false); + } + private final int below; + private final int above; + private final boolean between; /** @@ -58,45 +100,16 @@ public class UnicodeEscaper extends CodePointTranslator { } /** - * Constructs a {@link UnicodeEscaper} below the specified value (exclusive). + * Converts the given code point to a hexadecimal string of the form {@code "\\uXXXX"} * - * @param codePoint below which to escape - * @return the newly created {@link UnicodeEscaper} instance - */ - public static UnicodeEscaper below(final int codePoint) { - return outsideOf(codePoint, Integer.MAX_VALUE); - } - - /** - * Constructs a {@link UnicodeEscaper} above the specified value (exclusive). + * @param codePoint + * a Unicode code point + * @return the hexadecimal string for the given code point * - * @param codePoint above which to escape - * @return the newly created {@link UnicodeEscaper} instance + * @since 3.2 */ - public static UnicodeEscaper above(final int codePoint) { - return outsideOf(0, codePoint); - } - - /** - * Constructs a {@link UnicodeEscaper} outside of the specified values (exclusive). - * - * @param codePointLow below which to escape - * @param codePointHigh above which to escape - * @return the newly created {@link UnicodeEscaper} instance - */ - public static UnicodeEscaper outsideOf(final int codePointLow, final int codePointHigh) { - return new UnicodeEscaper(codePointLow, codePointHigh, false); - } - - /** - * Constructs a {@link UnicodeEscaper} between the specified values (inclusive). - * - * @param codePointLow above which to escape - * @param codePointHigh below which to escape - * @return the newly created {@link UnicodeEscaper} instance - */ - public static UnicodeEscaper between(final int codePointLow, final int codePointHigh) { - return new UnicodeEscaper(codePointLow, codePointHigh, true); + protected String toUtf16Escape(final int codePoint) { + return "\\u" + hex(codePoint); } /** @@ -124,17 +137,4 @@ public class UnicodeEscaper extends CodePointTranslator { } return true; } - - /** - * Converts the given code point to a hexadecimal string of the form {@code "\\uXXXX"} - * - * @param codePoint - * a Unicode code point - * @return the hexadecimal string for the given code point - * - * @since 3.2 - */ - protected String toUtf16Escape(final int codePoint) { - return "\\u" + hex(codePoint); - } } diff --git a/src/main/java/org/apache/commons/lang3/time/AbstractFormatCache.java b/src/main/java/org/apache/commons/lang3/time/AbstractFormatCache.java index eb8a9e6c0..a8f00227c 100644 --- a/src/main/java/org/apache/commons/lang3/time/AbstractFormatCache.java +++ b/src/main/java/org/apache/commons/lang3/time/AbstractFormatCache.java @@ -38,14 +38,160 @@ import org.apache.commons.lang3.LocaleUtils; // TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach. abstract class AbstractFormatCache { + /** + * Helper class to hold multipart Map keys as arrays. + */ + private static final class ArrayKey { + + private static int computeHashCode(final Object[] keys) { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(keys); + return result; + } + + private final Object[] keys; + private final int hashCode; + + /** + * Constructs an instance of {@link MultipartKey} to hold the specified objects. + * + * @param keys the set of objects that make up the key. Each key may be null. + */ + ArrayKey(final Object... keys) { + this.keys = keys; + this.hashCode = computeHashCode(keys); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ArrayKey other = (ArrayKey) obj; + return Arrays.deepEquals(keys, other.keys); + } + + @Override + public int hashCode() { + return hashCode; + } + + + } + /** * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG */ static final int NONE = -1; + private static final ConcurrentMap cDateTimeInstanceCache = new ConcurrentHashMap<>(7); + + /** + * Gets a date/time format for the specified styles and locale. + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format + * @param locale The non-null locale of the desired format + * @return a localized standard date/time format + * @throws IllegalArgumentException if the Locale has no date/time pattern defined + */ + // package protected, for access from test code; do not make public or protected + static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) { + final Locale safeLocale = LocaleUtils.toLocale(locale); + final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale); + return cDateTimeInstanceCache.computeIfAbsent(key, k -> { + try { + final DateFormat formatter; + if (dateStyle == null) { + formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale); + } else if (timeStyle == null) { + formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale); + } else { + formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale); + } + return ((SimpleDateFormat) formatter).toPattern(); + } catch (final ClassCastException ex) { + throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale); + } + }); + } + private final ConcurrentMap cInstanceCache = new ConcurrentHashMap<>(7); - private static final ConcurrentMap cDateTimeInstanceCache = new ConcurrentHashMap<>(7); + /** + * 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 {@code null} + */ + protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale); + + /** + * Gets a date formatter instance using the specified style, + * time zone and locale. + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date, null means use default Locale + * @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 + */ + // package protected, for access from FastDateFormat; do not make public or protected + F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) { + return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, 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, null means use default Locale + * @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 + */ + // package protected, for access from FastDateFormat; do not make public or protected + F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { + return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale); + } + + /** + * Gets a date/time formatter instance using the specified style, + * time zone and locale. + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format + * @param timeZone optional time zone, overrides time zone of + * formatted date, null means use default Locale + * @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 + */ + // This must remain private, see LANG-884 + private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { + locale = LocaleUtils.toLocale(locale); + final String pattern = getPatternForStyle(dateStyle, timeStyle, locale); + return getInstance(pattern, timeZone, locale); + } /** * Gets a formatter instance using the default pattern in the @@ -77,74 +223,6 @@ abstract class AbstractFormatCache { return cInstanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale)); } - /** - * 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 {@code null} - */ - protected abstract 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, null indicates no date in format - * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format - * @param timeZone optional time zone, overrides time zone of - * formatted date, null means use default Locale - * @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 - */ - // This must remain private, see LANG-884 - private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { - locale = LocaleUtils.toLocale(locale); - final String pattern = getPatternForStyle(dateStyle, timeStyle, locale); - return getInstance(pattern, timeZone, 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, null means use default Locale - * @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 - */ - // package protected, for access from FastDateFormat; do not make public or protected - F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { - return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale); - } - - /** - * Gets a date formatter instance using the specified style, - * time zone and locale. - * - * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT - * @param timeZone optional time zone, overrides time zone of - * formatted date, null means use default Locale - * @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 - */ - // package protected, for access from FastDateFormat; do not make public or protected - F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) { - return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale); - } - /** * Gets a time formatter instance using the specified style, * time zone and locale. @@ -162,82 +240,4 @@ abstract class AbstractFormatCache { return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale); } - /** - * Gets a date/time format for the specified styles and locale. - * - * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format - * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format - * @param locale The non-null locale of the desired format - * @return a localized standard date/time format - * @throws IllegalArgumentException if the Locale has no date/time pattern defined - */ - // package protected, for access from test code; do not make public or protected - static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) { - final Locale safeLocale = LocaleUtils.toLocale(locale); - final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale); - return cDateTimeInstanceCache.computeIfAbsent(key, k -> { - try { - final DateFormat formatter; - if (dateStyle == null) { - formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale); - } else if (timeStyle == null) { - formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale); - } else { - formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale); - } - return ((SimpleDateFormat) formatter).toPattern(); - } catch (final ClassCastException ex) { - throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale); - } - }); - } - - /** - * Helper class to hold multipart Map keys as arrays. - */ - private static final class ArrayKey { - - private static int computeHashCode(final Object[] keys) { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(keys); - return result; - } - - private final Object[] keys; - private final int hashCode; - - /** - * Constructs an instance of {@link MultipartKey} to hold the specified objects. - * - * @param keys the set of objects that make up the key. Each key may be null. - */ - ArrayKey(final Object... keys) { - this.keys = keys; - this.hashCode = computeHashCode(keys); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ArrayKey other = (ArrayKey) obj; - return Arrays.deepEquals(keys, other.keys); - } - - - } - } diff --git a/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java b/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java index 1688c31e4..87f6a5d30 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java @@ -197,15 +197,6 @@ public class DateFormatUtils { 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() { - } - /** * Formats a calendar into a specific pattern. The TimeZone from the calendar * will be used for formatting. @@ -412,4 +403,13 @@ public class DateFormatUtils { return calendar == null ? null : calendar.getTimeZone(); } + /** + * 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() { + } + } diff --git a/src/main/java/org/apache/commons/lang3/time/DateParser.java b/src/main/java/org/apache/commons/lang3/time/DateParser.java index e57146d7d..01af551ee 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateParser.java +++ b/src/main/java/org/apache/commons/lang3/time/DateParser.java @@ -35,6 +35,33 @@ import java.util.TimeZone; */ public interface DateParser { + /** + * Gets the locale used by this parser. + * + * @return the locale + */ + Locale getLocale(); + + // Accessors + /** + * Gets the pattern used by this parser. + * + * @return the pattern, {@link java.text.SimpleDateFormat} compatible + */ + String getPattern(); + + /** + * Gets the time zone used by this parser. + * + *

+ * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by + * the format pattern. + *

+ * + * @return the time zone + */ + TimeZone getTimeZone(); + /** * Equivalent to DateFormat.parse(String). * @@ -75,33 +102,6 @@ public interface DateParser { */ boolean parse(String source, ParsePosition pos, Calendar calendar); - // Accessors - /** - * Gets the pattern used by this parser. - * - * @return the pattern, {@link java.text.SimpleDateFormat} compatible - */ - String getPattern(); - - /** - * Gets the time zone used by this parser. - * - *

- * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by - * the format pattern. - *

- * - * @return the time zone - */ - TimeZone getTimeZone(); - - /** - * Gets the locale used by this parser. - * - * @return the locale - */ - Locale getLocale(); - /** * Parses text from a string to produce a Date. * diff --git a/src/main/java/org/apache/commons/lang3/time/DatePrinter.java b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java index 1ae6a01ce..72000a0d8 100644 --- a/src/main/java/org/apache/commons/lang3/time/DatePrinter.java +++ b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java @@ -35,23 +35,6 @@ import java.util.TimeZone; */ public interface DatePrinter { - /** - * Formats a millisecond {@code long} value. - * - * @param millis the millisecond value to format - * @return the formatted string - * @since 2.1 - */ - String format(long millis); - - /** - * Formats a {@link Date} object using a {@link GregorianCalendar}. - * - * @param date the date to format - * @return the formatted string - */ - String format(Date date); - /** * Formats a {@link Calendar} object. * The TimeZone set on the Calendar is only used to adjust the time offset. @@ -64,28 +47,18 @@ public interface DatePrinter { String format(Calendar calendar); /** - * Formats a millisecond {@code long} value into the - * supplied {@link StringBuffer}. + * Formats a {@link Calendar} object into the supplied {@link Appendable}. + * The TimeZone set on the Calendar is only used to adjust the time offset. + * The TimeZone specified during the construction of the Parser will determine the TimeZone + * used in the formatted string. * - * @param millis the millisecond value to format + * @param calendar the calendar to format * @param buf the buffer to format into + * @param the Appendable class type, usually StringBuilder or StringBuffer. * @return the specified string buffer - * @deprecated Use {{@link #format(long, Appendable)}. + * @since 3.5 */ - @Deprecated - StringBuffer format(long millis, StringBuffer buf); - - /** - * Formats a {@link Date} object into the - * supplied {@link StringBuffer} using a {@link GregorianCalendar}. - * - * @param date the date to format - * @param buf the buffer to format into - * @return the specified string buffer - * @deprecated Use {{@link #format(Date, Appendable)}. - */ - @Deprecated - StringBuffer format(Date date, StringBuffer buf); + B format(Calendar calendar, B buf); /** * Formats a {@link Calendar} object into the supplied {@link StringBuffer}. @@ -102,16 +75,12 @@ public interface DatePrinter { StringBuffer format(Calendar calendar, StringBuffer buf); /** - * Formats a millisecond {@code long} value into the - * supplied {@link Appendable}. + * Formats a {@link Date} object using a {@link GregorianCalendar}. * - * @param millis the millisecond value to format - * @param buf the buffer to format into - * @param the Appendable class type, usually StringBuilder or StringBuffer. - * @return the specified string buffer - * @since 3.5 + * @param date the date to format + * @return the formatted string */ - B format(long millis, B buf); + String format(Date date); /** * Formats a {@link Date} object into the @@ -126,19 +95,69 @@ public interface DatePrinter { B format(Date date, B buf); /** - * Formats a {@link Calendar} object into the supplied {@link Appendable}. - * The TimeZone set on the Calendar is only used to adjust the time offset. - * The TimeZone specified during the construction of the Parser will determine the TimeZone - * used in the formatted string. + * Formats a {@link Date} object into the + * supplied {@link StringBuffer} using a {@link GregorianCalendar}. * - * @param calendar the calendar to format + * @param date the date to format + * @param buf the buffer to format into + * @return the specified string buffer + * @deprecated Use {{@link #format(Date, Appendable)}. + */ + @Deprecated + StringBuffer format(Date date, StringBuffer buf); + + /** + * Formats a millisecond {@code long} value. + * + * @param millis the millisecond value to format + * @return the formatted string + * @since 2.1 + */ + String format(long millis); + + /** + * Formats a millisecond {@code long} value into the + * supplied {@link Appendable}. + * + * @param millis the millisecond value to format * @param buf the buffer to format into * @param the Appendable class type, usually StringBuilder or StringBuffer. * @return the specified string buffer * @since 3.5 */ - B format(Calendar calendar, B buf); + B format(long millis, B buf); + /** + * Formats a millisecond {@code long} value into the + * supplied {@link StringBuffer}. + * + * @param millis the millisecond value to format + * @param buf the buffer to format into + * @return the specified string buffer + * @deprecated Use {{@link #format(long, Appendable)}. + */ + @Deprecated + StringBuffer format(long millis, StringBuffer buf); + + + /** + * Formats a {@link Date}, {@link Calendar} or + * {@link 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 + * @see java.text.DateFormat#format(Object, StringBuffer, FieldPosition) + */ + StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos); + + /** + * Gets the locale used by this printer. + * + * @return the locale + */ + Locale getLocale(); // Accessors /** @@ -156,23 +175,4 @@ public interface DatePrinter { * @return the time zone */ TimeZone getTimeZone(); - - /** - * Gets the locale used by this printer. - * - * @return the locale - */ - Locale getLocale(); - - /** - * Formats a {@link Date}, {@link Calendar} or - * {@link 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 - * @see java.text.DateFormat#format(Object, StringBuffer, FieldPosition) - */ - StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos); } diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java index 383db9841..3583715ca 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java @@ -55,69 +55,59 @@ import org.apache.commons.lang3.LocaleUtils; public class DateUtils { /** - * Number of milliseconds in a standard second. - * @since 2.1 + * Date iterator. */ - 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; + static class DateIterator implements Iterator { + private final Calendar endFinal; + private final Calendar spot; - /** - * 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; + /** + * Constructs a DateIterator that ranges from one date to another. + * + * @param startFinal start date (inclusive) + * @param endFinal end date (inclusive) + */ + DateIterator(final Calendar startFinal, final Calendar endFinal) { + this.endFinal = endFinal; + spot = startFinal; + spot.add(Calendar.DATE, -1); + } - 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, SEMI_MONTH}, - {Calendar.YEAR}, - {Calendar.ERA}}; + /** + * Has the iterator not reached the end date yet? + * + * @return {@code true} if the iterator has yet to reach the end date + */ + @Override + public boolean hasNext() { + return spot.before(endFinal); + } - /** - * 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; + /** + * Returns the next calendar in the iteration + * + * @return Object calendar for the next date + */ + @Override + public Calendar next() { + if (spot.equals(endFinal)) { + throw new NoSuchElementException(); + } + spot.add(Calendar.DATE, 1); + return (Calendar) spot.clone(); + } + /** + * Always throws UnsupportedOperationException. + * + * @throws UnsupportedOperationException Always thrown. + * @see java.util.Iterator#remove() + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } /** * Calendar modification types. */ @@ -137,276 +127,88 @@ public class DateUtils { */ CEILING } - /** - * {@link 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() { - } - - /** - * 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 NullPointerException if either date is {@code null} + * Number of milliseconds in a standard second. * @since 2.1 */ - public static boolean isSameDay(final Date date1, final Date date2) { - return isSameDay(toCalendar(date1), toCalendar(date2)); - } - + public static final long MILLIS_PER_SECOND = 1000; /** - * 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 NullPointerException if either calendar is {@code null} + * Number of milliseconds in a standard minute. * @since 2.1 */ - public static boolean isSameDay(final Calendar cal1, final Calendar cal2) { - Objects.requireNonNull(cal1, "cal1"); - Objects.requireNonNull(cal2, "cal2"); - 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); - } + public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND; /** - * 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 NullPointerException if either date is {@code null} + * Number of milliseconds in a standard hour. * @since 2.1 */ - public static boolean isSameInstant(final Date date1, final Date date2) { - Objects.requireNonNull(date1, "date1"); - Objects.requireNonNull(date2, "date2"); - return date1.getTime() == date2.getTime(); - } + public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE; /** - * 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 NullPointerException if either date is {@code null} + * Number of milliseconds in a standard day. * @since 2.1 */ - public static boolean isSameInstant(final Calendar cal1, final Calendar cal2) { - Objects.requireNonNull(cal1, "cal1"); - Objects.requireNonNull(cal2, "cal2"); - return cal1.getTime().getTime() == cal2.getTime().getTime(); - } + public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR; /** - * 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 NullPointerException if either date is {@code null} - * @since 2.1 + * This is half a month, so this represents whether a date is in the top + * or bottom half of the month. */ - public static boolean isSameLocalTime(final Calendar cal1, final Calendar cal2) { - Objects.requireNonNull(cal1, "cal1"); - Objects.requireNonNull(cal2, "cal2"); - 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(); - } - + 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, SEMI_MONTH}, + {Calendar.YEAR}, + {Calendar.ERA}}; /** - * 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 NullPointerException if the date string or pattern array is null - * @throws ParseException if none of the date patterns were suitable (or there were none) + * A week range, starting on Sunday. */ - public static Date parseDate(final String str, final String... parsePatterns) throws ParseException { - return parseDate(str, null, parsePatterns); - } - + public static final int RANGE_WEEK_SUNDAY = 1; /** - * Parses a string representing a date by trying a variety of different parsers, - * using the default date format symbols for the given locale. - * - *

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 locale the locale whose date format symbols should be used. If {@code null}, - * the system locale is used (as per {@link #parseDate(String, String...)}). - * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null - * @return the parsed date - * @throws NullPointerException if the date string or pattern array is null - * @throws ParseException if none of the date patterns were suitable (or there were none) - * @since 3.2 + * A week range, starting on Monday. */ - public static Date parseDate(final String str, final Locale locale, final String... parsePatterns) throws ParseException { - return parseDateWithLeniency(str, locale, parsePatterns, true); - } - + public static final int RANGE_WEEK_MONDAY = 2; /** - * 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 NullPointerException if the date string or pattern array is null - * @throws ParseException if none of the date patterns were suitable - * @since 2.5 + * A week range, starting on the day focused. */ - public static Date parseDateStrictly(final String str, final String... parsePatterns) throws ParseException { - return parseDateStrictly(str, null, parsePatterns); - } - + public static final int RANGE_WEEK_RELATIVE = 3; /** - * Parses a string representing a date by trying a variety of different parsers, - * using the default date format symbols for the given locale.. - * - *

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 locale the locale whose date format symbols should be used. If {@code null}, - * the system locale is used (as per {@link #parseDateStrictly(String, String...)}). - * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null - * @return the parsed date - * @throws NullPointerException if the date string or pattern array is null - * @throws ParseException if none of the date patterns were suitable - * @since 3.2 + * A week range, centered around the day focused. */ - public static Date parseDateStrictly(final String str, final Locale locale, final String... parsePatterns) throws ParseException { - return parseDateWithLeniency(str, locale, parsePatterns, false); - } + public static final int RANGE_WEEK_CENTER = 4; /** - * 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 dateStr the date to parse, not null - * @param locale the locale to use when interpreting the pattern, can be null in which - * case the default system locale is used - * @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 NullPointerException if the date string or pattern array is null - * @throws ParseException if none of the date patterns were suitable - * @see java.util.Calendar#isLenient() + * A month range, the week starting on Sunday. */ - private static Date parseDateWithLeniency(final String dateStr, final Locale locale, final String[] parsePatterns, - final boolean lenient) throws ParseException { - Objects.requireNonNull(dateStr, "str"); - Objects.requireNonNull(parsePatterns, "parsePatterns"); - - final TimeZone tz = TimeZone.getDefault(); - final Locale lcl = LocaleUtils.toLocale(locale); - final ParsePosition pos = new ParsePosition(0); - final Calendar calendar = Calendar.getInstance(tz, lcl); - calendar.setLenient(lenient); - - for (final String parsePattern : parsePatterns) { - final FastDateParser fdp = new FastDateParser(parsePattern, tz, lcl); - calendar.clear(); - try { - if (fdp.parse(dateStr, pos, calendar) && pos.getIndex() == dateStr.length()) { - return calendar.getTime(); - } - } catch (final IllegalArgumentException ignored) { - // leniency is preventing calendar from being set - } - pos.setIndex(0); - } - throw new ParseException("Unable to parse the date: " + dateStr, -1); - } + public static final int RANGE_MONTH_SUNDAY = 5; /** - * Adds a number of years to a date returning a new object. + * A month range, the week starting on Monday. + */ + public static final int RANGE_MONTH_MONDAY = 6; + + /** + * Adds to a date returning a new object. * The original {@link 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 {@link Date} with the amount added * @throws NullPointerException if the date is null */ - public static Date addYears(final Date date, final int amount) { - return add(date, Calendar.YEAR, amount); - } - - /** - * Adds a number of months to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to add, may be negative - * @return the new {@link Date} with the amount added - * @throws NullPointerException if the date is null - */ - public static Date addMonths(final Date date, final int amount) { - return add(date, Calendar.MONTH, amount); - } - - /** - * Adds a number of weeks to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to add, may be negative - * @return the new {@link Date} with the amount added - * @throws NullPointerException if the date is null - */ - public static Date addWeeks(final Date date, final int amount) { - return add(date, Calendar.WEEK_OF_YEAR, amount); + private static Date add(final Date date, final int calendarField, final int amount) { + validateDateNotNull(date); + final Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(calendarField, amount); + return c.getTime(); } /** @@ -435,6 +237,19 @@ public class DateUtils { return add(date, Calendar.HOUR_OF_DAY, amount); } + /** + * Adds a number of milliseconds to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to add, may be negative + * @return the new {@link Date} with the amount added + * @throws NullPointerException if the date is null + */ + public static Date addMilliseconds(final Date date, final int amount) { + return add(date, Calendar.MILLISECOND, amount); + } + /** * Adds a number of minutes to a date returning a new object. * The original {@link Date} is unchanged. @@ -448,6 +263,19 @@ public class DateUtils { return add(date, Calendar.MINUTE, amount); } + /** + * Adds a number of months to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to add, may be negative + * @return the new {@link Date} with the amount added + * @throws NullPointerException if the date is null + */ + public static Date addMonths(final Date date, final int amount) { + return add(date, Calendar.MONTH, amount); + } + /** * Adds a number of seconds to a date returning a new object. * The original {@link Date} is unchanged. @@ -462,7 +290,7 @@ public class DateUtils { } /** - * Adds a number of milliseconds to a date returning a new object. + * Adds a number of weeks to a date returning a new object. * The original {@link Date} is unchanged. * * @param date the date, not null @@ -470,374 +298,21 @@ public class DateUtils { * @return the new {@link Date} with the amount added * @throws NullPointerException if the date is null */ - public static Date addMilliseconds(final Date date, final int amount) { - return add(date, Calendar.MILLISECOND, amount); + public static Date addWeeks(final Date date, final int amount) { + return add(date, Calendar.WEEK_OF_YEAR, amount); } /** - * Adds to a date returning a new object. + * Adds a number of years to a date returning a new object. * The original {@link 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 {@link Date} with the amount added * @throws NullPointerException if the date is null */ - private static Date add(final Date date, final int calendarField, final int amount) { - validateDateNotNull(date); - final 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 {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @since 2.4 - */ - public static Date setYears(final Date date, final int amount) { - return set(date, Calendar.YEAR, amount); - } - - /** - * Sets the months field to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 0 <= amount <= 11} - * @since 2.4 - */ - public static Date setMonths(final Date date, final int amount) { - return set(date, Calendar.MONTH, amount); - } - - /** - * Sets the day of month field to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 1 <= amount <= 31} - * @since 2.4 - */ - public static Date setDays(final Date date, final 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 {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 0 <= amount <= 23} - * @since 2.4 - */ - public static Date setHours(final Date date, final int amount) { - return set(date, Calendar.HOUR_OF_DAY, amount); - } - - /** - * Sets the minute field to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 0 <= amount <= 59} - * @since 2.4 - */ - public static Date setMinutes(final Date date, final int amount) { - return set(date, Calendar.MINUTE, amount); - } - - /** - * Sets the seconds field to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 0 <= amount <= 59} - * @since 2.4 - */ - public static Date setSeconds(final Date date, final int amount) { - return set(date, Calendar.SECOND, amount); - } - - /** - * Sets the milliseconds field to a date returning a new object. - * The original {@link Date} is unchanged. - * - * @param date the date, not null - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @throws IllegalArgumentException if {@code amount} is not in the range - * {@code 0 <= amount <= 999} - * @since 2.4 - */ - public static Date setMilliseconds(final Date date, final 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 {@link Date} is unchanged. - * - * @param date the date, not null - * @param calendarField the {@link Calendar} field to set the amount to - * @param amount the amount to set - * @return a new {@link Date} set with the specified value - * @throws NullPointerException if the date is null - * @since 2.4 - */ - private static Date set(final Date date, final int calendarField, final int amount) { - validateDateNotNull(date); - // getInstance() returns a new object, so this method is thread safe. - final Calendar c = Calendar.getInstance(); - c.setLenient(false); - c.setTime(date); - c.set(calendarField, amount); - return c.getTime(); - } - - /** - * Converts a {@link Date} into a {@link 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(final Date date) { - final Calendar c = Calendar.getInstance(); - c.setTime(Objects.requireNonNull(date, "date")); - return c; - } - - /** - * Converts a {@link Date} of a given {@link TimeZone} into a {@link Calendar} - * @param date the date to convert to a Calendar - * @param tz the time zone of the {@code date} - * @return the created Calendar - * @throws NullPointerException if {@code date} or {@code tz} is null - */ - public static Calendar toCalendar(final Date date, final TimeZone tz) { - final Calendar c = Calendar.getInstance(tz); - c.setTime(Objects.requireNonNull(date, "date")); - return c; - } - - /** - * Rounds a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 time zone 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 {@link Calendar} or {@code SEMI_MONTH} - * @return the different rounded date, not null - * @throws NullPointerException if the date is null - * @throws ArithmeticException if the year is over 280 million - */ - public static Date round(final Date date, final int field) { - return modify(toCalendar(date), field, ModifyType.ROUND).getTime(); - } - - /** - * Rounds a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 time zone 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 calendar the date to work with, not null - * @param field the field from {@link Calendar} or {@code SEMI_MONTH} - * @return the different rounded date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ArithmeticException if the year is over 280 million - */ - public static Calendar round(final Calendar calendar, final int field) { - Objects.requireNonNull(calendar, "calendar"); - return modify((Calendar) calendar.clone(), field, ModifyType.ROUND); - } - - /** - * Rounds a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 time zone 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 {@link Date} or {@link Calendar}, not null - * @param field the field from {@link Calendar} or {@code SEMI_MONTH} - * @return the different rounded date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ClassCastException if the object type is not a {@link Date} or {@link Calendar} - * @throws ArithmeticException if the year is over 280 million - */ - public static Date round(final Object date, final int field) { - Objects.requireNonNull(date, "date"); - if (date instanceof Date) { - return round((Date) date, field); - } - if (date instanceof Calendar) { - return round((Calendar) date, field).getTime(); - } - throw new ClassCastException("Could not round " + date); - } - - /** - * Truncates a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} - * @return the different truncated date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ArithmeticException if the year is over 280 million - */ - public static Date truncate(final Date date, final int field) { - return modify(toCalendar(date), field, ModifyType.TRUNCATE).getTime(); - } - - /** - * Truncates a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} - * @return the different truncated date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ArithmeticException if the year is over 280 million - */ - public static Calendar truncate(final Calendar date, final int field) { - Objects.requireNonNull(date, "date"); - return modify((Calendar) date.clone(), field, ModifyType.TRUNCATE); - } - - /** - * Truncates a date, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 {@link Date} or {@link Calendar}, not null - * @param field the field from {@link Calendar} or {@code SEMI_MONTH} - * @return the different truncated date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ClassCastException if the object type is not a {@link Date} or {@link Calendar} - * @throws ArithmeticException if the year is over 280 million - */ - public static Date truncate(final Object date, final int field) { - Objects.requireNonNull(date, "date"); - if (date instanceof Date) { - return truncate((Date) date, field); - } - if (date instanceof Calendar) { - return truncate((Calendar) date, field).getTime(); - } - throw new ClassCastException("Could not truncate " + date); - } - - /** - * Gets a date ceiling, leaving the field specified as the most - * significant field. - * - *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} - * @return the different ceil date, not null - * @throws NullPointerException if the date is {@code null} - * @throws ArithmeticException if the year is over 280 million - * @since 2.5 - */ - public static Date ceiling(final Date date, final int field) { - return modify(toCalendar(date), field, ModifyType.CEILING).getTime(); + public static Date addYears(final Date date, final int amount) { + return add(date, Calendar.YEAR, amount); } /** @@ -861,6 +336,26 @@ public class DateUtils { return modify((Calendar) calendar.clone(), field, ModifyType.CEILING); } + /** + * Gets a date ceiling, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} + * @return the different ceil date, not null + * @throws NullPointerException if the date is {@code null} + * @throws ArithmeticException if the year is over 280 million + * @since 2.5 + */ + public static Date ceiling(final Date date, final int field) { + return modify(toCalendar(date), field, ModifyType.CEILING).getTime(); + } + /** * Gets a date ceiling, leaving the field specified as the most * significant field. @@ -889,6 +384,704 @@ public class DateUtils { throw new ClassCastException("Could not find ceiling of for type: " + date.getClass()); } + /** + * Gets a Calendar fragment for any unit. + * + * @param calendar the calendar to work with, not null + * @param fragment the Calendar field part of calendar to calculate + * @param unit the time unit + * @return number of units within the fragment of the calendar + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + private static long getFragment(final Calendar calendar, final int fragment, final TimeUnit unit) { + Objects.requireNonNull(calendar, "calendar"); + long result = 0; + final int offset = (unit == TimeUnit.DAYS) ? 0 : 1; + + // Fragments bigger than a day require a breakdown to days + switch (fragment) { + case Calendar.YEAR: + result += unit.convert(calendar.get(Calendar.DAY_OF_YEAR) - offset, TimeUnit.DAYS); + break; + case Calendar.MONTH: + result += unit.convert(calendar.get(Calendar.DAY_OF_MONTH) - offset, TimeUnit.DAYS); + break; + default: + 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 += unit.convert(calendar.get(Calendar.HOUR_OF_DAY), TimeUnit.HOURS); + //$FALL-THROUGH$ + case Calendar.HOUR_OF_DAY: + result += unit.convert(calendar.get(Calendar.MINUTE), TimeUnit.MINUTES); + //$FALL-THROUGH$ + case Calendar.MINUTE: + result += unit.convert(calendar.get(Calendar.SECOND), TimeUnit.SECONDS); + //$FALL-THROUGH$ + case Calendar.SECOND: + result += unit.convert(calendar.get(Calendar.MILLISECOND), TimeUnit.MILLISECONDS); + break; + case Calendar.MILLISECOND: break; //never useful + default: throw new IllegalArgumentException("The fragment " + fragment + " is not supported"); + } + return result; + } + + /** + * Gets a Date fragment for any unit. + * + * @param date the date to work with, not null + * @param fragment the Calendar field part of date to calculate + * @param unit the time unit + * @return number of units within the fragment of the date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if fragment is not supported + * @since 2.4 + */ + private static long getFragment(final Date date, final int fragment, final TimeUnit unit) { + validateDateNotNull(date); + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return getFragment(calendar, fragment, unit); + } + + /** + * 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 {@link Calendar} field part of calendar to calculate + * @return number of days within the fragment of date + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInDays(final Calendar calendar, final int fragment) { + return getFragment(calendar, fragment, TimeUnit.DAYS); + } + + /** + * Returns the number of days within the + * fragment. All date fields 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 {@link Calendar} field part of date to calculate + * @return number of days within the fragment of date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the fragment is not supported + * @since 2.4 + */ + public static long getFragmentInDays(final Date date, final int fragment) { + return getFragment(date, fragment, TimeUnit.DAYS); + } + + /** + * Returns the number of hours within the + * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate + * @return number of hours within the fragment of date + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInHours(final Calendar calendar, final int fragment) { + return getFragment(calendar, fragment, TimeUnit.HOURS); + } + + /** + * Returns the number of hours within the + * fragment. All date fields 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 {@link Calendar} field part of date to calculate + * @return number of hours within the fragment of date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the fragment is not supported + * @since 2.4 + */ + public static long getFragmentInHours(final Date date, final int fragment) { + return getFragment(date, fragment, TimeUnit.HOURS); + } + + /** + * Returns the number of milliseconds within the + * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate + * @return number of milliseconds within the fragment of date + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMilliseconds(final Calendar calendar, final int fragment) { + return getFragment(calendar, fragment, TimeUnit.MILLISECONDS); + } + + /** + * Returns the number of milliseconds within the + * fragment. All date fields 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 {@link Calendar} field part of date to calculate + * @return number of milliseconds within the fragment of date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMilliseconds(final Date date, final int fragment) { + return getFragment(date, fragment, TimeUnit.MILLISECONDS); + } + + /** + * Returns the number of minutes within the + * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate + * @return number of minutes within the fragment of date + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMinutes(final Calendar calendar, final int fragment) { + return getFragment(calendar, fragment, TimeUnit.MINUTES); + } + + /** + * Returns the number of minutes within the + * fragment. All date fields 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 {@link Calendar} field part of date to calculate + * @return number of minutes within the fragment of date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the fragment is not supported + * @since 2.4 + */ + public static long getFragmentInMinutes(final Date date, final int fragment) { + return getFragment(date, fragment, TimeUnit.MINUTES); + } + + /** + * Returns the number of seconds within the + * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate + * @return number of seconds within the fragment of date + * @throws NullPointerException if the date is {@code null} or + * fragment is not supported + * @since 2.4 + */ + public static long getFragmentInSeconds(final Calendar calendar, final int fragment) { + return getFragment(calendar, fragment, TimeUnit.SECONDS); + } + + /** + * Returns the number of seconds within the + * fragment. All date fields 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 {@link Calendar} field part of date to calculate + * @return number of seconds within the fragment of date + * @throws NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the fragment is not supported + * @since 2.4 + */ + public static long getFragmentInSeconds(final Date date, final int fragment) { + return getFragment(date, fragment, TimeUnit.SECONDS); + } + + /** + * 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 NullPointerException if either calendar is {@code null} + * @since 2.1 + */ + public static boolean isSameDay(final Calendar cal1, final Calendar cal2) { + Objects.requireNonNull(cal1, "cal1"); + Objects.requireNonNull(cal2, "cal2"); + 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 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 NullPointerException if either date is {@code null} + * @since 2.1 + */ + public static boolean isSameDay(final Date date1, final Date date2) { + return isSameDay(toCalendar(date1), toCalendar(date2)); + } + + /** + * 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 NullPointerException if either date is {@code null} + * @since 2.1 + */ + public static boolean isSameInstant(final Calendar cal1, final Calendar cal2) { + Objects.requireNonNull(cal1, "cal1"); + Objects.requireNonNull(cal2, "cal2"); + return cal1.getTime().getTime() == cal2.getTime().getTime(); + } + + /** + * 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 NullPointerException if either date is {@code null} + * @since 2.1 + */ + public static boolean isSameInstant(final Date date1, final Date date2) { + Objects.requireNonNull(date1, "date1"); + Objects.requireNonNull(date2, "date2"); + return date1.getTime() == date2.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 NullPointerException if either date is {@code null} + * @since 2.1 + */ + public static boolean isSameLocalTime(final Calendar cal1, final Calendar cal2) { + Objects.requireNonNull(cal1, "cal1"); + Objects.requireNonNull(cal2, "cal2"); + 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(); + } + + /** + * Constructs an {@link 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 + * {@code RANGE_MONTH_SUNDAY} will return an {@link 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 calendar 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 NullPointerException if calendar is {@code null} + * @throws IllegalArgumentException if the rangeStyle is invalid + */ + public static Iterator iterator(final Calendar calendar, final int rangeStyle) { + Objects.requireNonNull(calendar, "calendar"); + final Calendar start; + final Calendar end; + 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(calendar, 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(calendar, Calendar.DATE); + end = truncate(calendar, 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 = calendar.get(Calendar.DAY_OF_WEEK); + endCutoff = startCutoff - 1; + break; + case RANGE_WEEK_CENTER: + startCutoff = calendar.get(Calendar.DAY_OF_WEEK) - 3; + endCutoff = calendar.get(Calendar.DAY_OF_WEEK) + 3; + break; + default: + 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); + } + + /** + * Constructs an {@link 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 + * {@code RANGE_MONTH_SUNDAY} will return an {@link 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 NullPointerException if the date is {@code null} + * @throws IllegalArgumentException if the rangeStyle is invalid + */ + public static Iterator iterator(final Date focus, final int rangeStyle) { + return iterator(toCalendar(focus), rangeStyle); + } + + /** + * Constructs an {@link 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 + * {@code RANGE_MONTH_SUNDAY} will return an {@link Iterator} + * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, + * 2002, returning a Calendar instance for each intermediate day.

+ * + * @param calendar the date to work with, either {@link Date} or {@link 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 NullPointerException if the date is {@code null} + * @throws ClassCastException if the object type is not a {@link Date} or {@link Calendar} + */ + public static Iterator iterator(final Object calendar, final int rangeStyle) { + Objects.requireNonNull(calendar, "calendar"); + if (calendar instanceof Date) { + return iterator((Date) calendar, rangeStyle); + } + if (calendar instanceof Calendar) { + return iterator((Calendar) calendar, rangeStyle); + } + throw new ClassCastException("Could not iterate based on " + calendar); + } + /** * Internal calculation method. * @@ -1038,637 +1231,454 @@ public class DateUtils { } /** - * Constructs an {@link Iterator} over each day in a date - * range defined by a focus date and range style. + * Parses a string representing a date by trying a variety of different parsers, + * using the default date format symbols for the given locale. * - *

For instance, passing Thursday, July 4, 2002 and a - * {@code RANGE_MONTH_SUNDAY} will return an {@link Iterator} - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

+ *

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

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 NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the rangeStyle is invalid + * @param str the date to parse, not null + * @param locale the locale whose date format symbols should be used. If {@code null}, + * the system locale is used (as per {@link #parseDate(String, String...)}). + * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null + * @return the parsed date + * @throws NullPointerException if the date string or pattern array is null + * @throws ParseException if none of the date patterns were suitable (or there were none) + * @since 3.2 */ - public static Iterator iterator(final Date focus, final int rangeStyle) { - return iterator(toCalendar(focus), rangeStyle); + public static Date parseDate(final String str, final Locale locale, final String... parsePatterns) throws ParseException { + return parseDateWithLeniency(str, locale, parsePatterns, true); } /** - * Constructs an {@link Iterator} over each day in a date - * range defined by a focus date and range style. + * Parses a string representing a date by trying a variety of different parsers. * - *

For instance, passing Thursday, July 4, 2002 and a - * {@code RANGE_MONTH_SUNDAY} will return an {@link Iterator} - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

+ *

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

This method provides an iterator that returns Calendar objects. - * The days are progressed using {@link Calendar#add(int, int)}.

+ * @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 NullPointerException 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(final String str, final String... parsePatterns) throws ParseException { + return parseDate(str, null, parsePatterns); + } + + /** + * Parses a string representing a date by trying a variety of different parsers, + * using the default date format symbols for the given locale.. + * + *

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 locale the locale whose date format symbols should be used. If {@code null}, + * the system locale is used (as per {@link #parseDateStrictly(String, String...)}). + * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null + * @return the parsed date + * @throws NullPointerException if the date string or pattern array is null + * @throws ParseException if none of the date patterns were suitable + * @since 3.2 + */ + public static Date parseDateStrictly(final String str, final Locale locale, final String... parsePatterns) throws ParseException { + return parseDateWithLeniency(str, locale, 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.

+ * 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 NullPointerException 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(final String str, final String... parsePatterns) throws ParseException { + return parseDateStrictly(str, null, parsePatterns); + } + + /** + * 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 dateStr the date to parse, not null + * @param locale the locale to use when interpreting the pattern, can be null in which + * case the default system locale is used + * @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 NullPointerException if the date string or pattern array is null + * @throws ParseException if none of the date patterns were suitable + * @see java.util.Calendar#isLenient() + */ + private static Date parseDateWithLeniency(final String dateStr, final Locale locale, final String[] parsePatterns, + final boolean lenient) throws ParseException { + Objects.requireNonNull(dateStr, "str"); + Objects.requireNonNull(parsePatterns, "parsePatterns"); + + final TimeZone tz = TimeZone.getDefault(); + final Locale lcl = LocaleUtils.toLocale(locale); + final ParsePosition pos = new ParsePosition(0); + final Calendar calendar = Calendar.getInstance(tz, lcl); + calendar.setLenient(lenient); + + for (final String parsePattern : parsePatterns) { + final FastDateParser fdp = new FastDateParser(parsePattern, tz, lcl); + calendar.clear(); + try { + if (fdp.parse(dateStr, pos, calendar) && pos.getIndex() == dateStr.length()) { + return calendar.getTime(); + } + } catch (final IllegalArgumentException ignored) { + // leniency is preventing calendar from being set + } + pos.setIndex(0); + } + throw new ParseException("Unable to parse the date: " + dateStr, -1); + } + + /** + * Rounds a date, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 time zone 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 calendar 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 NullPointerException if calendar is {@code null} - * @throws IllegalArgumentException if the rangeStyle is invalid + * @param field the field from {@link Calendar} or {@code SEMI_MONTH} + * @return the different rounded date, not null + * @throws NullPointerException if the date is {@code null} + * @throws ArithmeticException if the year is over 280 million */ - public static Iterator iterator(final Calendar calendar, final int rangeStyle) { + public static Calendar round(final Calendar calendar, final int field) { Objects.requireNonNull(calendar, "calendar"); - final Calendar start; - final Calendar end; - 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(calendar, 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(calendar, Calendar.DATE); - end = truncate(calendar, 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 = calendar.get(Calendar.DAY_OF_WEEK); - endCutoff = startCutoff - 1; - break; - case RANGE_WEEK_CENTER: - startCutoff = calendar.get(Calendar.DAY_OF_WEEK) - 3; - endCutoff = calendar.get(Calendar.DAY_OF_WEEK) + 3; - break; - default: - 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); + return modify((Calendar) calendar.clone(), field, ModifyType.ROUND); } /** - * Constructs an {@link Iterator} over each day in a date - * range defined by a focus date and range style. + * Rounds a date, leaving the field specified as the most + * significant field. * - *

For instance, passing Thursday, July 4, 2002 and a - * {@code RANGE_MONTH_SUNDAY} will return an {@link Iterator} - * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, - * 2002, returning a Calendar instance for each intermediate day.

+ *

For example, if you had the date-time 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.

* - * @param calendar the date to work with, either {@link Date} or {@link 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 + *

For a date in a time zone 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 {@link Calendar} or {@code SEMI_MONTH} + * @return the different rounded date, not null + * @throws NullPointerException if the date is null + * @throws ArithmeticException if the year is over 280 million + */ + public static Date round(final Date date, final int field) { + return modify(toCalendar(date), field, ModifyType.ROUND).getTime(); + } + + /** + * Rounds a date, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 time zone 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 {@link Date} or {@link Calendar}, not null + * @param field the field from {@link Calendar} or {@code SEMI_MONTH} + * @return the different rounded date, not null * @throws NullPointerException if the date is {@code null} * @throws ClassCastException if the object type is not a {@link Date} or {@link Calendar} + * @throws ArithmeticException if the year is over 280 million */ - public static Iterator iterator(final Object calendar, final int rangeStyle) { - Objects.requireNonNull(calendar, "calendar"); - if (calendar instanceof Date) { - return iterator((Date) calendar, rangeStyle); + public static Date round(final Object date, final int field) { + Objects.requireNonNull(date, "date"); + if (date instanceof Date) { + return round((Date) date, field); } - if (calendar instanceof Calendar) { - return iterator((Calendar) calendar, rangeStyle); + if (date instanceof Calendar) { + return round((Calendar) date, field).getTime(); } - throw new ClassCastException("Could not iterate based on " + calendar); + throw new ClassCastException("Could not round " + date); } /** - * Returns the number of milliseconds within the - * fragment. All date fields greater than the fragment will be ignored. + * Sets the specified field to a date returning a new object. + * This does not use a lenient calendar. + * The original {@link Date} is unchanged. * - *

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 {@link Calendar} field part of date to calculate - * @return number of milliseconds within the fragment of date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the fragment is not supported + * @param date the date, not null + * @param calendarField the {@link Calendar} field to set the amount to + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null * @since 2.4 */ - public static long getFragmentInMilliseconds(final Date date, final int fragment) { - return getFragment(date, fragment, TimeUnit.MILLISECONDS); - } - - /** - * Returns the number of seconds within the - * fragment. All date fields 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 {@link Calendar} field part of date to calculate - * @return number of seconds within the fragment of date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the fragment is not supported - * @since 2.4 - */ - public static long getFragmentInSeconds(final Date date, final int fragment) { - return getFragment(date, fragment, TimeUnit.SECONDS); - } - - /** - * Returns the number of minutes within the - * fragment. All date fields 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 {@link Calendar} field part of date to calculate - * @return number of minutes within the fragment of date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMinutes(final Date date, final int fragment) { - return getFragment(date, fragment, TimeUnit.MINUTES); - } - - /** - * Returns the number of hours within the - * fragment. All date fields 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 {@link Calendar} field part of date to calculate - * @return number of hours within the fragment of date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the fragment is not supported - * @since 2.4 - */ - public static long getFragmentInHours(final Date date, final int fragment) { - return getFragment(date, fragment, TimeUnit.HOURS); - } - - /** - * Returns the number of days within the - * fragment. All date fields 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 {@link Calendar} field part of date to calculate - * @return number of days within the fragment of date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if the fragment is not supported - * @since 2.4 - */ - public static long getFragmentInDays(final Date date, final int fragment) { - return getFragment(date, fragment, TimeUnit.DAYS); - } - - /** - * Returns the number of milliseconds within the - * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate - * @return number of milliseconds within the fragment of date - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMilliseconds(final Calendar calendar, final int fragment) { - return getFragment(calendar, fragment, TimeUnit.MILLISECONDS); - } - /** - * Returns the number of seconds within the - * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate - * @return number of seconds within the fragment of date - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInSeconds(final Calendar calendar, final int fragment) { - return getFragment(calendar, fragment, TimeUnit.SECONDS); - } - - /** - * Returns the number of minutes within the - * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate - * @return number of minutes within the fragment of date - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInMinutes(final Calendar calendar, final int fragment) { - return getFragment(calendar, fragment, TimeUnit.MINUTES); - } - - /** - * Returns the number of hours within the - * fragment. All date fields 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 {@link Calendar} field part of calendar to calculate - * @return number of hours within the fragment of date - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInHours(final Calendar calendar, final int fragment) { - return getFragment(calendar, fragment, TimeUnit.HOURS); - } - - /** - * 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 {@link Calendar} field part of calendar to calculate - * @return number of days within the fragment of date - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported - * @since 2.4 - */ - public static long getFragmentInDays(final Calendar calendar, final int fragment) { - return getFragment(calendar, fragment, TimeUnit.DAYS); - } - - /** - * Gets a Date fragment for any unit. - * - * @param date the date to work with, not null - * @param fragment the Calendar field part of date to calculate - * @param unit the time unit - * @return number of units within the fragment of the date - * @throws NullPointerException if the date is {@code null} - * @throws IllegalArgumentException if fragment is not supported - * @since 2.4 - */ - private static long getFragment(final Date date, final int fragment, final TimeUnit unit) { + private static Date set(final Date date, final int calendarField, final int amount) { validateDateNotNull(date); - final Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - return getFragment(calendar, fragment, unit); + // getInstance() returns a new object, so this method is thread safe. + final Calendar c = Calendar.getInstance(); + c.setLenient(false); + c.setTime(date); + c.set(calendarField, amount); + return c.getTime(); } /** - * Gets a Calendar fragment for any unit. + * Sets the day of month field to a date returning a new object. + * The original {@link Date} is unchanged. * - * @param calendar the calendar to work with, not null - * @param fragment the Calendar field part of calendar to calculate - * @param unit the time unit - * @return number of units within the fragment of the calendar - * @throws NullPointerException if the date is {@code null} or - * fragment is not supported + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 1 <= amount <= 31} * @since 2.4 */ - private static long getFragment(final Calendar calendar, final int fragment, final TimeUnit unit) { - Objects.requireNonNull(calendar, "calendar"); - long result = 0; - final int offset = (unit == TimeUnit.DAYS) ? 0 : 1; - - // Fragments bigger than a day require a breakdown to days - switch (fragment) { - case Calendar.YEAR: - result += unit.convert(calendar.get(Calendar.DAY_OF_YEAR) - offset, TimeUnit.DAYS); - break; - case Calendar.MONTH: - result += unit.convert(calendar.get(Calendar.DAY_OF_MONTH) - offset, TimeUnit.DAYS); - break; - default: - 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 += unit.convert(calendar.get(Calendar.HOUR_OF_DAY), TimeUnit.HOURS); - //$FALL-THROUGH$ - case Calendar.HOUR_OF_DAY: - result += unit.convert(calendar.get(Calendar.MINUTE), TimeUnit.MINUTES); - //$FALL-THROUGH$ - case Calendar.MINUTE: - result += unit.convert(calendar.get(Calendar.SECOND), TimeUnit.SECONDS); - //$FALL-THROUGH$ - case Calendar.SECOND: - result += unit.convert(calendar.get(Calendar.MILLISECOND), TimeUnit.MILLISECONDS); - break; - case Calendar.MILLISECOND: break; //never useful - default: throw new IllegalArgumentException("The fragment " + fragment + " is not supported"); - } - return result; + public static Date setDays(final Date date, final int amount) { + return set(date, Calendar.DAY_OF_MONTH, amount); } /** - * Determines if two calendars are equal up to no more than the specified - * most significant field. + * Sets the hours field to a date returning a new object. Hours range + * from 0-23. + * The original {@link Date} is unchanged. * - * @param cal1 the first calendar, not {@code null} - * @param cal2 the second calendar, not {@code null} - * @param field the field from {@link Calendar} - * @return {@code true} if equal; otherwise {@code false} - * @throws NullPointerException if any argument is {@code null} - * @see #truncate(Calendar, int) - * @see #truncatedEquals(Date, Date, int) - * @since 3.0 + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 0 <= amount <= 23} + * @since 2.4 */ - public static boolean truncatedEquals(final Calendar cal1, final Calendar cal2, final int field) { - return truncatedCompareTo(cal1, cal2, field) == 0; + public static Date setHours(final Date date, final int amount) { + return set(date, Calendar.HOUR_OF_DAY, amount); } /** - * Determines if two dates are equal up to no more than the specified - * most significant field. + * Sets the milliseconds field to a date returning a new object. + * The original {@link Date} is unchanged. * - * @param date1 the first date, not {@code null} - * @param date2 the second date, not {@code null} - * @param field the field from {@link Calendar} - * @return {@code true} if equal; otherwise {@code false} - * @throws NullPointerException if any argument is {@code null} - * @see #truncate(Date, int) - * @see #truncatedEquals(Calendar, Calendar, int) + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 0 <= amount <= 999} + * @since 2.4 + */ + public static Date setMilliseconds(final Date date, final int amount) { + return set(date, Calendar.MILLISECOND, amount); + } + + /** + * Sets the minute field to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 0 <= amount <= 59} + * @since 2.4 + */ + public static Date setMinutes(final Date date, final int amount) { + return set(date, Calendar.MINUTE, amount); + } + + /** + * Sets the months field to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 0 <= amount <= 11} + * @since 2.4 + */ + public static Date setMonths(final Date date, final int amount) { + return set(date, Calendar.MONTH, amount); + } + + /** + * Sets the seconds field to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @throws IllegalArgumentException if {@code amount} is not in the range + * {@code 0 <= amount <= 59} + * @since 2.4 + */ + public static Date setSeconds(final Date date, final int amount) { + return set(date, Calendar.SECOND, amount); + } + /** + * Sets the years field to a date returning a new object. + * The original {@link Date} is unchanged. + * + * @param date the date, not null + * @param amount the amount to set + * @return a new {@link Date} set with the specified value + * @throws NullPointerException if the date is null + * @since 2.4 + */ + public static Date setYears(final Date date, final int amount) { + return set(date, Calendar.YEAR, amount); + } + + /** + * Converts a {@link Date} into a {@link 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 boolean truncatedEquals(final Date date1, final Date date2, final int field) { - return truncatedCompareTo(date1, date2, field) == 0; + public static Calendar toCalendar(final Date date) { + final Calendar c = Calendar.getInstance(); + c.setTime(Objects.requireNonNull(date, "date")); + return c; + } + + /** + * Converts a {@link Date} of a given {@link TimeZone} into a {@link Calendar} + * @param date the date to convert to a Calendar + * @param tz the time zone of the {@code date} + * @return the created Calendar + * @throws NullPointerException if {@code date} or {@code tz} is null + */ + public static Calendar toCalendar(final Date date, final TimeZone tz) { + final Calendar c = Calendar.getInstance(tz); + c.setTime(Objects.requireNonNull(date, "date")); + return c; + } + + /** + * Truncates a date, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} + * @return the different truncated date, not null + * @throws NullPointerException if the date is {@code null} + * @throws ArithmeticException if the year is over 280 million + */ + public static Calendar truncate(final Calendar date, final int field) { + Objects.requireNonNull(date, "date"); + return modify((Calendar) date.clone(), field, ModifyType.TRUNCATE); + } + + /** + * Truncates a date, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 {@link Calendar} or {@code SEMI_MONTH} + * @return the different truncated date, not null + * @throws NullPointerException if the date is {@code null} + * @throws ArithmeticException if the year is over 280 million + */ + public static Date truncate(final Date date, final int field) { + return modify(toCalendar(date), field, ModifyType.TRUNCATE).getTime(); + } + + /** + * Truncates a date, leaving the field specified as the most + * significant field. + * + *

For example, if you had the date-time 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 {@link Date} or {@link Calendar}, not null + * @param field the field from {@link Calendar} or {@code SEMI_MONTH} + * @return the different truncated date, not null + * @throws NullPointerException if the date is {@code null} + * @throws ClassCastException if the object type is not a {@link Date} or {@link Calendar} + * @throws ArithmeticException if the year is over 280 million + */ + public static Date truncate(final Object date, final int field) { + Objects.requireNonNull(date, "date"); + if (date instanceof Date) { + return truncate((Date) date, field); + } + if (date instanceof Calendar) { + return truncate((Calendar) date, field).getTime(); + } + throw new ClassCastException("Could not truncate " + date); } /** @@ -1711,6 +1721,40 @@ public class DateUtils { return truncatedDate1.compareTo(truncatedDate2); } + /** + * Determines if two calendars are equal up to no more than the specified + * most significant field. + * + * @param cal1 the first calendar, not {@code null} + * @param cal2 the second calendar, not {@code null} + * @param field the field from {@link Calendar} + * @return {@code true} if equal; otherwise {@code false} + * @throws NullPointerException if any argument is {@code null} + * @see #truncate(Calendar, int) + * @see #truncatedEquals(Date, Date, int) + * @since 3.0 + */ + public static boolean truncatedEquals(final Calendar cal1, final Calendar cal2, final int field) { + return truncatedCompareTo(cal1, cal2, field) == 0; + } + + /** + * Determines if two dates are equal up to no more than the specified + * most significant field. + * + * @param date1 the first date, not {@code null} + * @param date2 the second date, not {@code null} + * @param field the field from {@link Calendar} + * @return {@code true} if equal; otherwise {@code false} + * @throws NullPointerException if any argument is {@code null} + * @see #truncate(Date, int) + * @see #truncatedEquals(Calendar, Calendar, int) + * @since 3.0 + */ + public static boolean truncatedEquals(final Date date1, final Date date2, final int field) { + return truncatedCompareTo(date1, date2, field) == 0; + } + /** * @param date Date to validate. * @throws NullPointerException if {@code date == null} @@ -1720,58 +1764,14 @@ public class DateUtils { } /** - * Date iterator. + * {@link 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.

*/ - 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 (inclusive) - */ - DateIterator(final Calendar startFinal, final Calendar endFinal) { - this.endFinal = endFinal; - spot = startFinal; - spot.add(Calendar.DATE, -1); - } - - /** - * Has the iterator not reached the end date yet? - * - * @return {@code true} if the iterator has yet to reach the end date - */ - @Override - public boolean hasNext() { - return spot.before(endFinal); - } - - /** - * Returns the next calendar in the iteration - * - * @return Object calendar for the next date - */ - @Override - public Calendar next() { - if (spot.equals(endFinal)) { - throw new NoSuchElementException(); - } - spot.add(Calendar.DATE, 1); - return (Calendar) spot.clone(); - } - - /** - * Always throws UnsupportedOperationException. - * - * @throws UnsupportedOperationException Always thrown. - * @see java.util.Iterator#remove() - */ - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + public DateUtils() { } } diff --git a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java index 00173ce98..574fe7db6 100644 --- a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java @@ -80,12 +80,116 @@ import org.apache.commons.lang3.Validate; 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.

+ * Element that is parsed from the format pattern. */ - public DurationFormatUtils() { + static class Token { + + /** Empty array. */ + private static final Token[] EMPTY_ARRAY = {}; + + /** + * Helper method to determine if a set of tokens contain a value + * + * @param tokens set to look in + * @param value to look for + * @return boolean {@code true} if contained + */ + static boolean containsTokenWithValue(final Token[] tokens, final Object value) { + return Stream.of(tokens).anyMatch(token -> token.getValue() == value); + } + + private final Object value; + private int count; + private int optionalIndex = -1; + + /** + * Wraps a token around a value. A value would be something like a 'Y'. + * + * @param value value to wrap, non-null. + * @param optional whether the token is optional + * @param optionalIndex the index of the optional token within the pattern + */ + Token(final Object value, final boolean optional, final int optionalIndex) { + this.value = Objects.requireNonNull(value, "value"); + this.count = 1; + if (optional) { + this.optionalIndex = optionalIndex; + } + } + + /** + * Supports equality of this Token to another Token. + * + * @param obj2 Object to consider equality of + * @return boolean {@code true} if equal + */ + @Override + public boolean equals(final Object obj2) { + if (obj2 instanceof Token) { + final Token tok2 = (Token) obj2; + if (this.value.getClass() != tok2.value.getClass()) { + return false; + } + if (this.count != tok2.count) { + return false; + } + if (this.value instanceof StringBuilder) { + return this.value.toString().equals(tok2.value.toString()); + } + if (this.value instanceof Number) { + return this.value.equals(tok2.value); + } + return this.value == tok2.value; + } + return false; + } + + /** + * 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, non-null. + */ + Object getValue() { + return value; + } + + /** + * 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(); + } + + /** + * Adds another one of the value + */ + void increment() { + count++; + } + + /** + * Represents this token as a String. + * + * @return String representation of the token + */ + @Override + public String toString() { + return StringUtils.repeat(this.value.toString(), this.count); + } } /** @@ -97,33 +201,123 @@ public class DurationFormatUtils { */ public static final String ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.SSS'S'"; - /** - * Formats the time gap as a string. - * - *

The format used is ISO 8601-like: {@code HH:mm:ss.SSS}.

- * - * @param durationMillis the duration to format - * @return the formatted duration, not null - * @throws IllegalArgumentException if durationMillis is negative - */ - public static String formatDurationHMS(final long durationMillis) { - return formatDuration(durationMillis, "HH:mm:ss.SSS"); - } + static final String y = "y"; + + static final String M = "M"; + + static final String d = "d"; + + static final String H = "H"; + + static final String m = "m"; + + static final String s = "s"; + + static final String S = "S"; /** - * Formats the time gap as a string. + * The internal method to do the formatting. * - *

The format used is the ISO 8601 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 - * @throws IllegalArgumentException if durationMillis is negative + * @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 */ - public static String formatDurationISO(final long durationMillis) { - return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false); + static String format(final Token[] tokens, final long years, final long months, final long days, final long hours, final long minutes, + final long seconds, + final long milliseconds, final boolean padWithZeros) { + final StringBuilder buffer = new StringBuilder(); + boolean lastOutputSeconds = false; + boolean lastOutputZero = false; + int optionalStart = -1; + boolean firstOptionalNonLiteral = false; + int optionalIndex = -1; + boolean inOptional = false; + for (final Token token : tokens) { + final Object value = token.getValue(); + final boolean isLiteral = value instanceof StringBuilder; + final int count = token.getCount(); + if (optionalIndex != token.optionalIndex) { + optionalIndex = token.optionalIndex; + if (optionalIndex > -1) { + //entering new optional block + optionalStart = buffer.length(); + lastOutputZero = false; + inOptional = true; + firstOptionalNonLiteral = false; + } else { + //leaving optional block + inOptional = false; + } + } + if (isLiteral) { + if (!inOptional || !lastOutputZero) { + buffer.append(value.toString()); + } + } else if (value.equals(y)) { + lastOutputSeconds = false; + lastOutputZero = years == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(years, padWithZeros, count)); + } + } else if (value.equals(M)) { + lastOutputSeconds = false; + lastOutputZero = months == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(months, padWithZeros, count)); + } + } else if (value.equals(d)) { + lastOutputSeconds = false; + lastOutputZero = days == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(days, padWithZeros, count)); + } + } else if (value.equals(H)) { + lastOutputSeconds = false; + lastOutputZero = hours == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(hours, padWithZeros, count)); + } + } else if (value.equals(m)) { + lastOutputSeconds = false; + lastOutputZero = minutes == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(minutes, padWithZeros, count)); + } + } else if (value.equals(s)) { + lastOutputSeconds = true; + lastOutputZero = seconds == 0; + if (!inOptional || !lastOutputZero) { + buffer.append(paddedValue(seconds, padWithZeros, count)); + } + } else if (value.equals(S)) { + lastOutputZero = milliseconds == 0; + if (!inOptional || !lastOutputZero) { + if (lastOutputSeconds) { + // ensure at least 3 digits are displayed even if padding is not selected + final int width = padWithZeros ? Math.max(3, count) : 3; + buffer.append(paddedValue(milliseconds, true, width)); + } else { + buffer.append(paddedValue(milliseconds, padWithZeros, count)); + } + } + lastOutputSeconds = false; + } + //as soon as we hit first nonliteral in optional, check for literal prefix + if (inOptional && !isLiteral && !firstOptionalNonLiteral){ + firstOptionalNonLiteral = true; + if (lastOutputZero) { + buffer.delete(optionalStart, buffer.length()); + } + } + } + return buffer.toString(); } /** @@ -185,6 +379,33 @@ public class DurationFormatUtils { return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros); } + /** + * Formats the time gap as a string. + * + *

The format used is ISO 8601-like: {@code HH:mm:ss.SSS}.

+ * + * @param durationMillis the duration to format + * @return the formatted duration, not null + * @throws IllegalArgumentException if durationMillis is negative + */ + public static String formatDurationHMS(final long durationMillis) { + return formatDuration(durationMillis, "HH:mm:ss.SSS"); + } + /** + * Formats the time gap as a string. + * + *

The format used is the ISO 8601 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 + * @throws IllegalArgumentException if durationMillis is negative + */ + public static String formatDurationISO(final long durationMillis) { + return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false); + } /** * Formats an elapsed time into a pluralization correct string. * @@ -246,21 +467,6 @@ public class DurationFormatUtils { duration = StringUtils.replaceOnce(duration, " 1 days", " 1 day"); return duration.trim(); } - - /** - * Formats the time gap as a string. - * - *

The format used is the ISO 8601 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 - * @throws IllegalArgumentException if startMillis is greater than endMillis - */ - public static String formatPeriodISO(final long startMillis, final 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. @@ -274,7 +480,6 @@ public class DurationFormatUtils { public static String formatPeriod(final long startMillis, final long endMillis, final 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 @@ -430,134 +635,19 @@ public class DurationFormatUtils { return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros); } - /** - * The internal method to do the formatting. + * Formats the time gap as a string. * - * @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(final Token[] tokens, final long years, final long months, final long days, final long hours, final long minutes, - final long seconds, - final long milliseconds, final boolean padWithZeros) { - final StringBuilder buffer = new StringBuilder(); - boolean lastOutputSeconds = false; - boolean lastOutputZero = false; - int optionalStart = -1; - boolean firstOptionalNonLiteral = false; - int optionalIndex = -1; - boolean inOptional = false; - for (final Token token : tokens) { - final Object value = token.getValue(); - final boolean isLiteral = value instanceof StringBuilder; - final int count = token.getCount(); - if (optionalIndex != token.optionalIndex) { - optionalIndex = token.optionalIndex; - if (optionalIndex > -1) { - //entering new optional block - optionalStart = buffer.length(); - lastOutputZero = false; - inOptional = true; - firstOptionalNonLiteral = false; - } else { - //leaving optional block - inOptional = false; - } - } - if (isLiteral) { - if (!inOptional || !lastOutputZero) { - buffer.append(value.toString()); - } - } else if (value.equals(y)) { - lastOutputSeconds = false; - lastOutputZero = years == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(years, padWithZeros, count)); - } - } else if (value.equals(M)) { - lastOutputSeconds = false; - lastOutputZero = months == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(months, padWithZeros, count)); - } - } else if (value.equals(d)) { - lastOutputSeconds = false; - lastOutputZero = days == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(days, padWithZeros, count)); - } - } else if (value.equals(H)) { - lastOutputSeconds = false; - lastOutputZero = hours == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(hours, padWithZeros, count)); - } - } else if (value.equals(m)) { - lastOutputSeconds = false; - lastOutputZero = minutes == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(minutes, padWithZeros, count)); - } - } else if (value.equals(s)) { - lastOutputSeconds = true; - lastOutputZero = seconds == 0; - if (!inOptional || !lastOutputZero) { - buffer.append(paddedValue(seconds, padWithZeros, count)); - } - } else if (value.equals(S)) { - lastOutputZero = milliseconds == 0; - if (!inOptional || !lastOutputZero) { - if (lastOutputSeconds) { - // ensure at least 3 digits are displayed even if padding is not selected - final int width = padWithZeros ? Math.max(3, count) : 3; - buffer.append(paddedValue(milliseconds, true, width)); - } else { - buffer.append(paddedValue(milliseconds, padWithZeros, count)); - } - } - lastOutputSeconds = false; - } - //as soon as we hit first nonliteral in optional, check for literal prefix - if (inOptional && !isLiteral && !firstOptionalNonLiteral){ - firstOptionalNonLiteral = true; - if (lastOutputZero) { - buffer.delete(optionalStart, buffer.length()); - } - } - } - return buffer.toString(); - } - - /** - * Converts a {@code long} to a {@link String} with optional - * zero padding. + *

The format used is the ISO 8601 period format.

* - * @param value the value to convert - * @param padWithZeros whether to pad with zeroes - * @param count the size to pad to (ignored if {@code padWithZeros} is false) - * @return the string result + * @param startMillis the start of the duration to format + * @param endMillis the end of the duration to format + * @return the formatted duration, not null + * @throws IllegalArgumentException if startMillis is greater than endMillis */ - private static String paddedValue(final long value, final boolean padWithZeros, final int count) { - final String longString = Long.toString(value); - return padWithZeros ? StringUtils.leftPad(longString, count, '0') : longString; + public static String formatPeriodISO(final long startMillis, final long endMillis) { + return formatPeriod(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault()); } - - static final String y = "y"; - static final String M = "M"; - static final String d = "d"; - static final String H = "H"; - static final String m = "m"; - static final String s = "s"; - static final String S = "S"; - /** * Parses a classic date format string into Tokens * @@ -656,116 +746,26 @@ public class DurationFormatUtils { } /** - * Element that is parsed from the format pattern. + * Converts a {@code long} to a {@link String} with optional + * zero padding. + * + * @param value the value to convert + * @param padWithZeros whether to pad with zeroes + * @param count the size to pad to (ignored if {@code padWithZeros} is false) + * @return the string result */ - static class Token { + private static String paddedValue(final long value, final boolean padWithZeros, final int count) { + final String longString = Long.toString(value); + return padWithZeros ? StringUtils.leftPad(longString, count, '0') : longString; + } - /** Empty array. */ - private static final Token[] EMPTY_ARRAY = {}; - - /** - * Helper method to determine if a set of tokens contain a value - * - * @param tokens set to look in - * @param value to look for - * @return boolean {@code true} if contained - */ - static boolean containsTokenWithValue(final Token[] tokens, final Object value) { - return Stream.of(tokens).anyMatch(token -> token.getValue() == value); - } - - private final Object value; - private int count; - private int optionalIndex = -1; - - /** - * Wraps a token around a value. A value would be something like a 'Y'. - * - * @param value value to wrap, non-null. - * @param optional whether the token is optional - * @param optionalIndex the index of the optional token within the pattern - */ - Token(final Object value, final boolean optional, final int optionalIndex) { - this.value = Objects.requireNonNull(value, "value"); - this.count = 1; - if (optional) { - this.optionalIndex = optionalIndex; - } - } - - /** - * 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, non-null. - */ - Object getValue() { - return value; - } - - /** - * Supports equality of this Token to another Token. - * - * @param obj2 Object to consider equality of - * @return boolean {@code true} if equal - */ - @Override - public boolean equals(final Object obj2) { - if (obj2 instanceof Token) { - final Token tok2 = (Token) obj2; - if (this.value.getClass() != tok2.value.getClass()) { - return false; - } - if (this.count != tok2.count) { - return false; - } - if (this.value instanceof StringBuilder) { - return this.value.toString().equals(tok2.value.toString()); - } - if (this.value instanceof Number) { - return this.value.equals(tok2.value); - } - 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); - } + /** + * 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() { } } diff --git a/src/main/java/org/apache/commons/lang3/time/DurationUtils.java b/src/main/java/org/apache/commons/lang3/time/DurationUtils.java index 2a6d7ebd7..080945490 100644 --- a/src/main/java/org/apache/commons/lang3/time/DurationUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DurationUtils.java @@ -106,6 +106,12 @@ public class DurationUtils { return !duration.isNegative() && !duration.isZero(); } + private static Instant now(final FailableConsumer nowConsumer) throws E { + final Instant start = Instant.now(); + nowConsumer.accept(start); + return start; + } + /** * Runs the lambda and returns the duration of its execution. * @@ -132,12 +138,6 @@ public class DurationUtils { return of(start -> runnable.run()); } - private static Instant now(final FailableConsumer nowConsumer) throws E { - final Instant start = Instant.now(); - nowConsumer.accept(start); - return start; - } - /** * Computes the Duration between a start instant and now. * diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java index 29ba451d1..d3e016e43 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java @@ -108,81 +108,6 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { } }; - /** Our fast printer. */ - private final FastDatePrinter printer; - - /** Our fast parser. */ - private final FastDateParser parser; - - /** - * Gets a formatter instance using the default pattern in the - * default locale. - * - * @return a date/time formatter - */ - public static FastDateFormat getInstance() { - return cache.getInstance(); - } - - /** - * 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(final 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(final String pattern, final 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(final String pattern, final 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(final String pattern, final TimeZone timeZone, final Locale locale) { - return cache.getInstance(pattern, timeZone, locale); - } - /** * Gets a date formatter instance using the specified style in the * default time zone and locale. @@ -244,6 +169,141 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { return cache.getDateInstance(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(final int dateStyle, final 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(final int dateStyle, final int timeStyle, final 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(final int dateStyle, final int timeStyle, final 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( + final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale); + } + + /** + * Gets a formatter instance using the default pattern in the + * default locale. + * + * @return a date/time formatter + */ + public static FastDateFormat getInstance() { + return cache.getInstance(); + } + + /** + * 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(final String pattern) { + return cache.getInstance(pattern, null, 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(final String pattern, final Locale locale) { + return cache.getInstance(pattern, null, locale); + } + + /** + * 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(final String pattern, final TimeZone timeZone) { + return cache.getInstance(pattern, timeZone, null); + } + + /** + * 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(final String pattern, final TimeZone timeZone, final Locale locale) { + return cache.getInstance(pattern, timeZone, locale); + } + /** * Gets a time formatter instance using the specified style in the * default time zone and locale. @@ -305,70 +365,10 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { return cache.getTimeInstance(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(final int dateStyle, final 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(final int dateStyle, final int timeStyle, final 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(final int dateStyle, final int timeStyle, final 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( - final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { - return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale); - } + /** Our fast printer. */ + private final FastDatePrinter printer; + /** Our fast parser. */ + private final FastDateParser parser; // Constructor /** @@ -398,43 +398,35 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { parser = new FastDateParser(pattern, timeZone, locale, centuryStart); } - // Format methods /** - * Formats a {@link Date}, {@link Calendar} or - * {@link Long} (milliseconds) object. - * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)} + * Performs the formatting by applying the rules to the + * specified calendar. * - * @param obj the object to format - * @param toAppendTo the buffer to append to - * @param pos the position - ignored - * @return the buffer passed in + * @param calendar the calendar to format + * @param buf the buffer to format into + * @return the specified string buffer + * @deprecated Use {@link #format(Calendar, Appendable)} */ - @Override - public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { - return toAppendTo.append(printer.format(obj)); + @Deprecated + protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { + return printer.applyRules(calendar, buf); } + // Basics /** - * Formats a millisecond {@code long} value. + * Compares two objects for equality. * - * @param millis the millisecond value to format - * @return the formatted string - * @since 2.1 + * @param obj the object to compare to + * @return {@code true} if equal */ @Override - public String format(final long millis) { - return printer.format(millis); - } - - /** - * Formats a {@link Date} object using a {@link GregorianCalendar}. - * - * @param date the date to format - * @return the formatted string - */ - @Override - public String format(final Date date) { - return printer.format(date); + public boolean equals(final Object obj) { + if (!(obj instanceof FastDateFormat)) { + return false; + } + final FastDateFormat other = (FastDateFormat) obj; + // no need to check parser, as it has same invariants as printer + return printer.equals(other.printer); } /** @@ -449,34 +441,17 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { } /** - * Formats a millisecond {@code long} value into the + * Formats a {@link Calendar} object into the * supplied {@link StringBuffer}. * - * @param millis the millisecond value to format + * @param calendar the calendar to format * @param buf the buffer to format into * @return the specified string buffer - * @since 2.1 - * @deprecated Use {{@link #format(long, Appendable)}. - */ - @Deprecated + * @since 3.5 + */ @Override - public StringBuffer format(final long millis, final StringBuffer buf) { - return printer.format(millis, buf); - } - - /** - * Formats a {@link Date} object into the - * supplied {@link StringBuffer} using a {@link GregorianCalendar}. - * - * @param date the date to format - * @param buf the buffer to format into - * @return the specified string buffer - * @deprecated Use {{@link #format(Date, Appendable)}. - */ - @Deprecated - @Override - public StringBuffer format(final Date date, final StringBuffer buf) { - return printer.format(date, buf); + public B format(final Calendar calendar, final B buf) { + return printer.format(calendar, buf); } /** @@ -495,17 +470,14 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { } /** - * Formats a millisecond {@code long} value into the - * supplied {@link StringBuffer}. + * Formats a {@link Date} object using a {@link GregorianCalendar}. * - * @param millis the millisecond value to format - * @param buf the buffer to format into - * @return the specified string buffer - * @since 3.5 + * @param date the date to format + * @return the formatted string */ @Override - public B format(final long millis, final B buf) { - return printer.format(millis, buf); + public String format(final Date date) { + return printer.format(date); } /** @@ -523,22 +495,137 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { } /** - * Formats a {@link Calendar} object into the + * Formats a {@link Date} object into the + * supplied {@link StringBuffer} using a {@link GregorianCalendar}. + * + * @param date the date to format + * @param buf the buffer to format into + * @return the specified string buffer + * @deprecated Use {{@link #format(Date, Appendable)}. + */ + @Deprecated + @Override + public StringBuffer format(final Date date, final StringBuffer buf) { + return printer.format(date, buf); + } + + /** + * Formats a millisecond {@code long} value. + * + * @param millis the millisecond value to format + * @return the formatted string + * @since 2.1 + */ + @Override + public String format(final long millis) { + return printer.format(millis); + } + + /** + * Formats a millisecond {@code long} value into the * supplied {@link StringBuffer}. * - * @param calendar the calendar to format + * @param millis the millisecond value to format * @param buf the buffer to format into * @return the specified string buffer * @since 3.5 - */ + */ @Override - public B format(final Calendar calendar, final B buf) { - return printer.format(calendar, buf); + public B format(final long millis, final B buf) { + return printer.format(millis, buf); } // Parsing + /** + * Formats a millisecond {@code long} value into the + * supplied {@link StringBuffer}. + * + * @param millis the millisecond value to format + * @param buf the buffer to format into + * @return the specified string buffer + * @since 2.1 + * @deprecated Use {{@link #format(long, Appendable)}. + */ + @Deprecated + @Override + public StringBuffer format(final long millis, final StringBuffer buf) { + return printer.format(millis, buf); + } + + // Format methods + /** + * Formats a {@link Date}, {@link Calendar} or + * {@link Long} (milliseconds) object. + * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)} + * + * @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(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { + return toAppendTo.append(printer.format(obj)); + } + + /** + * Gets the locale used by this formatter. + * + * @return the locale + */ + @Override + public Locale getLocale() { + return printer.getLocale(); + } + + /** + * 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 printer.getMaxLengthEstimate(); + } + + // Accessors + /** + * Gets the pattern used by this formatter. + * + * @return the pattern, {@link java.text.SimpleDateFormat} compatible + */ + @Override + public String getPattern() { + return printer.getPattern(); + } + + /** + * Gets the time zone used by this formatter. + * + *

This zone is always used for {@link Date} formatting.

+ * + * @return the time zone + */ + @Override + public TimeZone getTimeZone() { + return printer.getTimeZone(); + } + + /** + * Returns a hash code compatible with equals. + * + * @return a hash code compatible with equals + */ + @Override + public int hashCode() { + return printer.hashCode(); + } + /* (non-Javadoc) * @see DateParser#parse(String) */ @@ -572,79 +659,6 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { return parser.parseObject(source, pos); } - // Accessors - /** - * Gets the pattern used by this formatter. - * - * @return the pattern, {@link java.text.SimpleDateFormat} compatible - */ - @Override - public String getPattern() { - return printer.getPattern(); - } - - /** - * Gets the time zone used by this formatter. - * - *

This zone is always used for {@link Date} formatting.

- * - * @return the time zone - */ - @Override - public TimeZone getTimeZone() { - return printer.getTimeZone(); - } - - /** - * Gets the locale used by this formatter. - * - * @return the locale - */ - @Override - public Locale getLocale() { - return printer.getLocale(); - } - - /** - * 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 printer.getMaxLengthEstimate(); - } - - // Basics - /** - * Compares two objects for equality. - * - * @param obj the object to compare to - * @return {@code true} if equal - */ - @Override - public boolean equals(final Object obj) { - if (!(obj instanceof FastDateFormat)) { - return false; - } - final FastDateFormat other = (FastDateFormat) obj; - // no need to check parser, as it has same invariants as printer - return printer.equals(other.printer); - } - - /** - * Returns a hash code compatible with equals. - * - * @return a hash code compatible with equals - */ - @Override - public int hashCode() { - return printer.hashCode(); - } - /** * Gets a debugging string version of this formatter. * @@ -654,18 +668,4 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter { public String toString() { return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]"; } - - /** - * 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 - * @deprecated Use {@link #format(Calendar, Appendable)} - */ - @Deprecated - protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { - return printer.applyRules(calendar, buf); - } } diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java index 07ee62746..89aac5804 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java @@ -82,109 +82,310 @@ import org.apache.commons.lang3.LocaleUtils; public class FastDateParser implements DateParser, Serializable { /** - * Required for serialization support. - * - * @see java.io.Serializable + * A strategy that handles a text field in the parsing pattern */ - private static final long serialVersionUID = 3L; + private static final class CaseInsensitiveTextStrategy extends PatternStrategy { + private final int field; + final Locale locale; + private final Map lKeyValues; - static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP"); + /** + * Constructs a Strategy that parses a Text field + * + * @param field The Calendar field + * @param definingCalendar The Calendar to use + * @param locale The Locale to use + */ + CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) { + this.field = field; + this.locale = LocaleUtils.toLocale(locale); - /** Input pattern. */ - private final String pattern; - - /** Input TimeZone. */ - private final TimeZone timeZone; - - /** Input Locale. */ - private final Locale locale; - - /** - * Century from Date. - */ - private final int century; - - /** - * Start year from Date. - */ - private final int startYear; - - /** Initialized from Calendar. */ - private transient List patterns; - - /** - * comparator used to sort regex alternatives. Alternatives should be ordered longer first, and shorter last. ('february' before 'feb'). All entries must be - * lower-case by locale. - */ - private static final Comparator LONGER_FIRST_LOWERCASE = Comparator.reverseOrder(); - - /** - * Constructs a new FastDateParser. - * - * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the factory methods of {@link FastDateFormat} to get a cached - * FastDateParser instance. - * - * @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern - * @param timeZone non-null time zone to use - * @param locale non-null locale - */ - protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) { - this(pattern, timeZone, locale, null); - } - - /** - * Constructs a new FastDateParser. - * - * @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern - * @param timeZone non-null time zone to use - * @param locale locale, null maps to the default Locale. - * @param centuryStart The start of the century for 2 digit year parsing - * - * @since 3.5 - */ - protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { - this.pattern = Objects.requireNonNull(pattern, "pattern"); - this.timeZone = Objects.requireNonNull(timeZone, "timeZone"); - this.locale = LocaleUtils.toLocale(locale); - - final Calendar definingCalendar = Calendar.getInstance(timeZone, this.locale); - - final int centuryStartYear; - if (centuryStart != null) { - definingCalendar.setTime(centuryStart); - centuryStartYear = definingCalendar.get(Calendar.YEAR); - } else if (this.locale.equals(JAPANESE_IMPERIAL)) { - centuryStartYear = 0; - } else { - // from 80 years ago to 20 years from now - definingCalendar.setTime(new Date()); - centuryStartYear = definingCalendar.get(Calendar.YEAR) - 80; + final StringBuilder regex = new StringBuilder(); + regex.append("((?iu)"); + lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex); + regex.setLength(regex.length() - 1); + regex.append(")"); + createPattern(regex); } - century = centuryStartYear / 100 * 100; - startYear = centuryStartYear - century; - init(definingCalendar); - } - - /** - * Initializes derived fields from defining fields. This is called from constructor and from readObject (de-serialization) - * - * @param definingCalendar the {@link java.util.Calendar} instance used to initialize this FastDateParser - */ - private void init(final Calendar definingCalendar) { - patterns = new ArrayList<>(); - - final StrategyParser strategyParser = new StrategyParser(definingCalendar); - for (;;) { - final StrategyAndWidth field = strategyParser.getNextStrategy(); - if (field == null) { - break; + /** + * {@inheritDoc} + */ + @Override + void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) { + final String lowerCase = value.toLowerCase(locale); + Integer iVal = lKeyValues.get(lowerCase); + if (iVal == null) { + // match missing the optional trailing period + iVal = lKeyValues.get(lowerCase + '.'); } - patterns.add(field); + // LANG-1669: Mimic fix done in OpenJDK 17 to resolve issue with parsing newly supported day periods added in OpenJDK 16 + if (Calendar.AM_PM != this.field || iVal <= 1) { + calendar.set(field, iVal.intValue()); + } + } + + /** + * Converts this instance to a handy debug string. + * + * @since 3.12.0 + */ + @Override + public String toString() { + return "CaseInsensitiveTextStrategy [field=" + field + ", locale=" + locale + ", lKeyValues=" + lKeyValues + ", pattern=" + pattern + "]"; } } - // helper classes to parse the format string + /** + * A strategy that copies the static or quoted field in the parsing pattern + */ + private static final class CopyQuotedStrategy extends Strategy { + + private final String formatField; + + /** + * Constructs a Strategy that ensures the formatField has literal text + * + * @param formatField The literal text to match + */ + CopyQuotedStrategy(final String formatField) { + this.formatField = formatField; + } + + /** + * {@inheritDoc} + */ + @Override + boolean isNumber() { + return false; + } + + @Override + boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { + for (int idx = 0; idx < formatField.length(); ++idx) { + final int sIdx = idx + pos.getIndex(); + if (sIdx == source.length()) { + pos.setErrorIndex(sIdx); + return false; + } + if (formatField.charAt(idx) != source.charAt(sIdx)) { + pos.setErrorIndex(sIdx); + return false; + } + } + pos.setIndex(formatField.length() + pos.getIndex()); + return true; + } + + /** + * Converts this instance to a handy debug string. + * + * @since 3.12.0 + */ + @Override + public String toString() { + return "CopyQuotedStrategy [formatField=" + formatField + "]"; + } + } + + private static final class ISO8601TimeZoneStrategy extends PatternStrategy { + // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm + + private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))"); + + private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))"); + + private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))"); + /** + * Factory method for ISO8601TimeZoneStrategies. + * + * @param tokenLen a token indicating the length of the TimeZone String to be formatted. + * @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such strategy exists, an IllegalArgumentException + * will be thrown. + */ + static Strategy getStrategy(final int tokenLen) { + switch (tokenLen) { + case 1: + return ISO_8601_1_STRATEGY; + case 2: + return ISO_8601_2_STRATEGY; + case 3: + return ISO_8601_3_STRATEGY; + default: + throw new IllegalArgumentException("invalid number of X"); + } + } + /** + * Constructs a Strategy that parses a TimeZone + * + * @param pattern The Pattern + */ + ISO8601TimeZoneStrategy(final String pattern) { + createPattern(pattern); + } + + /** + * {@inheritDoc} + */ + @Override + void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) { + calendar.setTimeZone(FastTimeZone.getGmtTimeZone(value)); + } + } + + /** + * A strategy that handles a number field in the parsing pattern + */ + private static class NumberStrategy extends Strategy { + + private final int field; + + /** + * Constructs a Strategy that parses a Number field + * + * @param field The Calendar field + */ + NumberStrategy(final int field) { + this.field = field; + } + + /** + * {@inheritDoc} + */ + @Override + boolean isNumber() { + return true; + } + + /** + * Make any modifications to parsed integer + * + * @param parser The parser + * @param iValue The parsed integer + * @return The modified value + */ + int modify(final FastDateParser parser, final int iValue) { + return iValue; + } + + @Override + boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { + int idx = pos.getIndex(); + int last = source.length(); + + if (maxWidth == 0) { + // if no maxWidth, strip leading white space + for (; idx < last; ++idx) { + final char c = source.charAt(idx); + if (!Character.isWhitespace(c)) { + break; + } + } + pos.setIndex(idx); + } else { + final int end = idx + maxWidth; + if (last > end) { + last = end; + } + } + + for (; idx < last; ++idx) { + final char c = source.charAt(idx); + if (!Character.isDigit(c)) { + break; + } + } + + if (pos.getIndex() == idx) { + pos.setErrorIndex(idx); + return false; + } + + final int value = Integer.parseInt(source.substring(pos.getIndex(), idx)); + pos.setIndex(idx); + + calendar.set(field, modify(parser, value)); + return true; + } + + /** + * Converts this instance to a handy debug string. + * + * @since 3.12.0 + */ + @Override + public String toString() { + return "NumberStrategy [field=" + field + "]"; + } + } + + /** + * A strategy to parse a single field from the parsing pattern + */ + private abstract static class PatternStrategy extends Strategy { + + Pattern pattern; + + void createPattern(final String regex) { + this.pattern = Pattern.compile(regex); + } + + void createPattern(final StringBuilder regex) { + createPattern(regex.toString()); + } + + /** + * Is this field a number? The default implementation returns false. + * + * @return true, if field is a number + */ + @Override + boolean isNumber() { + return false; + } + + @Override + boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { + final Matcher matcher = pattern.matcher(source.substring(pos.getIndex())); + if (!matcher.lookingAt()) { + pos.setErrorIndex(pos.getIndex()); + return false; + } + pos.setIndex(pos.getIndex() + matcher.end(1)); + setCalendar(parser, calendar, matcher.group(1)); + return true; + } + + abstract void setCalendar(FastDateParser parser, Calendar calendar, String value); + + /** + * Converts this instance to a handy debug string. + * + * @since 3.12.0 + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [pattern=" + pattern + "]"; + } + + } + + /** + * A strategy to parse a single field from the parsing pattern + */ + private abstract static class Strategy { + + /** + * Is this field a number? The default implementation returns false. + * + * @return true, if field is a number + */ + boolean isNumber() { + return false; + } + + abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth); + } /** * Holds strategy and field width @@ -275,625 +476,10 @@ public class FastDateParser implements DateParser, Serializable { } } - private static boolean isFormatLetter(final char c) { - return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; - } - - // Accessors - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#getPattern() - */ - @Override - public String getPattern() { - return pattern; - } - - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#getTimeZone() - */ - @Override - public TimeZone getTimeZone() { - return timeZone; - } - - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#getLocale() - */ - @Override - public Locale getLocale() { - return locale; - } - - // Basics - /** - * Compares another object for equality with this object. - * - * @param obj the object to compare to - * @return {@code true}if equal to this instance - */ - @Override - public boolean equals(final Object obj) { - if (!(obj instanceof FastDateParser)) { - return false; - } - final FastDateParser other = (FastDateParser) obj; - return pattern.equals(other.pattern) && timeZone.equals(other.timeZone) && locale.equals(other.locale); - } - - /** - * Returns a hash code compatible with equals. - * - * @return a hash code compatible with equals - */ - @Override - public int hashCode() { - return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode()); - } - - /** - * Gets a string version of this formatter. - * - * @return a debugging string - */ - @Override - public String toString() { - return "FastDateParser[" + pattern + ", " + locale + ", " + timeZone.getID() + "]"; - } - - /** - * Converts all state of this instance to a String handy for debugging. - * - * @return a string. - * @since 3.12.0 - */ - public String toStringAll() { - return "FastDateParser [pattern=" + pattern + ", timeZone=" + timeZone + ", locale=" + locale + ", century=" + century + ", startYear=" + startYear - + ", patterns=" + patterns + "]"; - } - - // Serializing - /** - * Creates 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(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); - init(definingCalendar); - } - - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#parseObject(String) - */ - @Override - public Object parseObject(final String source) throws ParseException { - return parse(source); - } - - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#parse(String) - */ - @Override - public Date parse(final String source) throws ParseException { - final ParsePosition pp = new ParsePosition(0); - final Date date = parse(source, pp); - if (date == null) { - // Add a note regarding supported date range - if (locale.equals(JAPANESE_IMPERIAL)) { - throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\nUnparseable date: \"" + source, - pp.getErrorIndex()); - } - throw new ParseException("Unparseable date: " + source, pp.getErrorIndex()); - } - return date; - } - - /* - * (non-Javadoc) - * - * @see org.apache.commons.lang3.time.DateParser#parseObject(String, java.text.ParsePosition) - */ - @Override - public Object parseObject(final String source, final ParsePosition pos) { - return parse(source, pos); - } - - /** - * This implementation updates the ParsePosition if the parse succeeds. However, it sets the error index to the position before the failed field unlike the - * method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets the error index to after the failed field. - *

- * To determine if the parse has succeeded, the caller must check if the current parse position given by {@link ParsePosition#getIndex()} has been updated. - * If the input buffer has been fully parsed, then the index will point to just after the end of the input buffer. - * - * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition) - */ - @Override - public Date parse(final String source, final ParsePosition pos) { - // timing tests indicate getting new instance is 19% faster than cloning - final Calendar cal = Calendar.getInstance(timeZone, locale); - cal.clear(); - - return parse(source, pos, cal) ? cal.getTime() : null; - } - - /** - * Parses a formatted date string according to the format. Updates the Calendar with parsed fields. Upon success, the ParsePosition index is updated to - * indicate how much of the source text was consumed. Not all source text needs to be consumed. Upon parse failure, ParsePosition error index is updated to - * the offset of the source text which does not match the supplied format. - * - * @param source The text to parse. - * @param pos On input, the position in the source to start parsing, on output, updated position. - * @param calendar The calendar into which to set parsed fields. - * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated) - * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is out of range. - */ - @Override - public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) { - final ListIterator lt = patterns.listIterator(); - while (lt.hasNext()) { - final StrategyAndWidth strategyAndWidth = lt.next(); - final int maxWidth = strategyAndWidth.getMaxWidth(lt); - if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) { - return false; - } - } - return true; - } - - // Support for strategies - - private static StringBuilder simpleQuote(final StringBuilder sb, final String value) { - for (int i = 0; i < value.length(); ++i) { - final char c = value.charAt(i); - switch (c) { - case '\\': - case '^': - case '$': - case '.': - case '|': - case '?': - case '*': - case '+': - case '(': - case ')': - case '[': - case '{': - sb.append('\\'); - default: - sb.append(c); - } - } - if (sb.charAt(sb.length() - 1) == '.') { - // trailing '.' is optional - sb.append('?'); - } - return sb; - } - - /** - * Gets the short and long values displayed for a field - * - * @param calendar The calendar to obtain the short and long values - * @param locale The locale of display names - * @param field The field of interest - * @param regex The regular expression to build - * @return The map of string display names to field values - */ - private static Map appendDisplayNames(final Calendar calendar, final Locale locale, final int field, final StringBuilder regex) { - Objects.requireNonNull(calendar, "calendar"); - final Map values = new HashMap<>(); - final Locale actualLocale = LocaleUtils.toLocale(locale); - final Map displayNames = calendar.getDisplayNames(field, Calendar.ALL_STYLES, actualLocale); - final TreeSet sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE); - displayNames.forEach((k, v) -> { - final String keyLc = k.toLowerCase(actualLocale); - if (sorted.add(keyLc)) { - values.put(keyLc, v); - } - }); - sorted.forEach(symbol -> simpleQuote(regex, symbol).append('|')); - return values; - } - - /** - * Adjusts dates to be within appropriate century - * - * @param twoDigitYear The year to adjust - * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive) - */ - private int adjustYear(final int twoDigitYear) { - final int trial = century + twoDigitYear; - return twoDigitYear >= startYear ? trial : trial + 100; - } - - /** - * A strategy to parse a single field from the parsing pattern - */ - private abstract static class Strategy { - - /** - * Is this field a number? The default implementation returns false. - * - * @return true, if field is a number - */ - boolean isNumber() { - return false; - } - - abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth); - } - - /** - * A strategy to parse a single field from the parsing pattern - */ - private abstract static class PatternStrategy extends Strategy { - - Pattern pattern; - - void createPattern(final StringBuilder regex) { - createPattern(regex.toString()); - } - - void createPattern(final String regex) { - this.pattern = Pattern.compile(regex); - } - - /** - * Is this field a number? The default implementation returns false. - * - * @return true, if field is a number - */ - @Override - boolean isNumber() { - return false; - } - - @Override - boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { - final Matcher matcher = pattern.matcher(source.substring(pos.getIndex())); - if (!matcher.lookingAt()) { - pos.setErrorIndex(pos.getIndex()); - return false; - } - pos.setIndex(pos.getIndex() + matcher.end(1)); - setCalendar(parser, calendar, matcher.group(1)); - return true; - } - - abstract void setCalendar(FastDateParser parser, Calendar calendar, String value); - - /** - * Converts this instance to a handy debug string. - * - * @since 3.12.0 - */ - @Override - public String toString() { - return getClass().getSimpleName() + " [pattern=" + pattern + "]"; - } - - } - - /** - * Gets a Strategy given a field from a SimpleDateFormat pattern - * - * @param f A sub-sequence of the SimpleDateFormat pattern - * @param width formatting width - * @param definingCalendar The calendar to obtain the short and long values - * @return The Strategy that will handle parsing for the field - */ - private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) { - switch (f) { - default: - throw new IllegalArgumentException("Format '" + f + "' not supported"); - case 'D': - return DAY_OF_YEAR_STRATEGY; - case 'E': - return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar); - case 'F': - return DAY_OF_WEEK_IN_MONTH_STRATEGY; - case 'G': - return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar); - case 'H': // Hour in day (0-23) - return HOUR_OF_DAY_STRATEGY; - case 'K': // Hour in am/pm (0-11) - return HOUR_STRATEGY; - case 'M': - case 'L': - return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY; - case 'S': - return MILLISECOND_STRATEGY; - case 'W': - return WEEK_OF_MONTH_STRATEGY; - case 'a': - return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar); - case 'd': - return DAY_OF_MONTH_STRATEGY; - case 'h': // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0 - return HOUR12_STRATEGY; - case 'k': // Hour in day (1-24), i.e. midnight is 24, not 0 - return HOUR24_OF_DAY_STRATEGY; - case 'm': - return MINUTE_STRATEGY; - case 's': - return SECOND_STRATEGY; - case 'u': - return DAY_OF_WEEK_STRATEGY; - case 'w': - return WEEK_OF_YEAR_STRATEGY; - case 'y': - case 'Y': - return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY; - case 'X': - return ISO8601TimeZoneStrategy.getStrategy(width); - case 'Z': - if (width == 2) { - return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY; - } - //$FALL-THROUGH$ - case 'z': - return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar); - } - } - - @SuppressWarnings("unchecked") // OK because we are creating an array with no entries - private static final ConcurrentMap[] caches = new ConcurrentMap[Calendar.FIELD_COUNT]; - - /** - * Gets a cache of Strategies for a particular field - * - * @param field The Calendar field - * @return a cache of Locale to Strategy - */ - private static ConcurrentMap getCache(final int field) { - synchronized (caches) { - if (caches[field] == null) { - caches[field] = new ConcurrentHashMap<>(3); - } - return caches[field]; - } - } - - /** - * Constructs a Strategy that parses a Text field - * - * @param field The Calendar field - * @param definingCalendar The calendar to obtain the short and long values - * @return a TextStrategy for the field and Locale - */ - private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) { - final ConcurrentMap cache = getCache(field); - return cache.computeIfAbsent(locale, - k -> field == Calendar.ZONE_OFFSET ? new TimeZoneStrategy(locale) : new CaseInsensitiveTextStrategy(field, definingCalendar, locale)); - } - - /** - * A strategy that copies the static or quoted field in the parsing pattern - */ - private static final class CopyQuotedStrategy extends Strategy { - - private final String formatField; - - /** - * Constructs a Strategy that ensures the formatField has literal text - * - * @param formatField The literal text to match - */ - CopyQuotedStrategy(final String formatField) { - this.formatField = formatField; - } - - /** - * {@inheritDoc} - */ - @Override - boolean isNumber() { - return false; - } - - @Override - boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { - for (int idx = 0; idx < formatField.length(); ++idx) { - final int sIdx = idx + pos.getIndex(); - if (sIdx == source.length()) { - pos.setErrorIndex(sIdx); - return false; - } - if (formatField.charAt(idx) != source.charAt(sIdx)) { - pos.setErrorIndex(sIdx); - return false; - } - } - pos.setIndex(formatField.length() + pos.getIndex()); - return true; - } - - /** - * Converts this instance to a handy debug string. - * - * @since 3.12.0 - */ - @Override - public String toString() { - return "CopyQuotedStrategy [formatField=" + formatField + "]"; - } - } - - /** - * A strategy that handles a text field in the parsing pattern - */ - private static final class CaseInsensitiveTextStrategy extends PatternStrategy { - private final int field; - final Locale locale; - private final Map lKeyValues; - - /** - * Constructs a Strategy that parses a Text field - * - * @param field The Calendar field - * @param definingCalendar The Calendar to use - * @param locale The Locale to use - */ - CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) { - this.field = field; - this.locale = LocaleUtils.toLocale(locale); - - final StringBuilder regex = new StringBuilder(); - regex.append("((?iu)"); - lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex); - regex.setLength(regex.length() - 1); - regex.append(")"); - createPattern(regex); - } - - /** - * {@inheritDoc} - */ - @Override - void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) { - final String lowerCase = value.toLowerCase(locale); - Integer iVal = lKeyValues.get(lowerCase); - if (iVal == null) { - // match missing the optional trailing period - iVal = lKeyValues.get(lowerCase + '.'); - } - // LANG-1669: Mimic fix done in OpenJDK 17 to resolve issue with parsing newly supported day periods added in OpenJDK 16 - if (Calendar.AM_PM != this.field || iVal <= 1) { - calendar.set(field, iVal.intValue()); - } - } - - /** - * Converts this instance to a handy debug string. - * - * @since 3.12.0 - */ - @Override - public String toString() { - return "CaseInsensitiveTextStrategy [field=" + field + ", locale=" + locale + ", lKeyValues=" + lKeyValues + ", pattern=" + pattern + "]"; - } - } - - /** - * A strategy that handles a number field in the parsing pattern - */ - private static class NumberStrategy extends Strategy { - - private final int field; - - /** - * Constructs a Strategy that parses a Number field - * - * @param field The Calendar field - */ - NumberStrategy(final int field) { - this.field = field; - } - - /** - * {@inheritDoc} - */ - @Override - boolean isNumber() { - return true; - } - - @Override - boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) { - int idx = pos.getIndex(); - int last = source.length(); - - if (maxWidth == 0) { - // if no maxWidth, strip leading white space - for (; idx < last; ++idx) { - final char c = source.charAt(idx); - if (!Character.isWhitespace(c)) { - break; - } - } - pos.setIndex(idx); - } else { - final int end = idx + maxWidth; - if (last > end) { - last = end; - } - } - - for (; idx < last; ++idx) { - final char c = source.charAt(idx); - if (!Character.isDigit(c)) { - break; - } - } - - if (pos.getIndex() == idx) { - pos.setErrorIndex(idx); - return false; - } - - final int value = Integer.parseInt(source.substring(pos.getIndex(), idx)); - pos.setIndex(idx); - - calendar.set(field, modify(parser, value)); - return true; - } - - /** - * Make any modifications to parsed integer - * - * @param parser The parser - * @param iValue The parsed integer - * @return The modified value - */ - int modify(final FastDateParser parser, final int iValue) { - return iValue; - } - - /** - * Converts this instance to a handy debug string. - * - * @since 3.12.0 - */ - @Override - public String toString() { - return "NumberStrategy [field=" + field + "]"; - } - } - - private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) { - /** - * {@inheritDoc} - */ - @Override - int modify(final FastDateParser parser, final int iValue) { - return iValue < 100 ? parser.adjustYear(iValue) : iValue; - } - }; - /** * A strategy that handles a time zone field in the parsing pattern */ static class TimeZoneStrategy extends PatternStrategy { - private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}"; - private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}"; - - private final Locale locale; - private final Map tzNames = new HashMap<>(); - private static final class TzInfo { final TimeZone zone; final int dstOffset; @@ -908,12 +494,18 @@ public class FastDateParser implements DateParser, Serializable { return "TzInfo [zone=" + zone + ", dstOffset=" + dstOffset + "]"; } } + private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}"; + private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}"; /** * Index of zone id */ private static final int ID = 0; + private final Locale locale; + + private final Map tzNames = new HashMap<>(); + /** * Constructs a Strategy that parses a TimeZone * @@ -1017,50 +609,35 @@ public class FastDateParser implements DateParser, Serializable { } - private static final class ISO8601TimeZoneStrategy extends PatternStrategy { - // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 3L; - /** - * Constructs a Strategy that parses a TimeZone - * - * @param pattern The Pattern - */ - ISO8601TimeZoneStrategy(final String pattern) { - createPattern(pattern); - } + static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP"); + /** + * comparator used to sort regex alternatives. Alternatives should be ordered longer first, and shorter last. ('february' before 'feb'). All entries must be + * lower-case by locale. + */ + private static final Comparator LONGER_FIRST_LOWERCASE = Comparator.reverseOrder(); + + // helper classes to parse the format string + + @SuppressWarnings("unchecked") // OK because we are creating an array with no entries + private static final ConcurrentMap[] caches = new ConcurrentMap[Calendar.FIELD_COUNT]; + + private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) { /** * {@inheritDoc} */ @Override - void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) { - calendar.setTimeZone(FastTimeZone.getGmtTimeZone(value)); + int modify(final FastDateParser parser, final int iValue) { + return iValue < 100 ? parser.adjustYear(iValue) : iValue; } - - private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))"); - private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))"); - private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))"); - - /** - * Factory method for ISO8601TimeZoneStrategies. - * - * @param tokenLen a token indicating the length of the TimeZone String to be formatted. - * @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such strategy exists, an IllegalArgumentException - * will be thrown. - */ - static Strategy getStrategy(final int tokenLen) { - switch (tokenLen) { - case 1: - return ISO_8601_1_STRATEGY; - case 2: - return ISO_8601_2_STRATEGY; - case 3: - return ISO_8601_3_STRATEGY; - default: - throw new IllegalArgumentException("invalid number of X"); - } - } - } + }; private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) { @Override @@ -1070,10 +647,15 @@ public class FastDateParser implements DateParser, Serializable { }; private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR); + private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR); + private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH); + private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR); + private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH); + private static final Strategy DAY_OF_WEEK_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK) { @Override int modify(final FastDateParser parser, final int iValue) { @@ -1082,7 +664,9 @@ public class FastDateParser implements DateParser, Serializable { }; private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH); + private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY); + private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) { @Override int modify(final FastDateParser parser, final int iValue) { @@ -1098,7 +682,423 @@ public class FastDateParser implements DateParser, Serializable { }; private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR); + private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE); + private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND); + + // Support for strategies + private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND); + + /** + * Gets the short and long values displayed for a field + * + * @param calendar The calendar to obtain the short and long values + * @param locale The locale of display names + * @param field The field of interest + * @param regex The regular expression to build + * @return The map of string display names to field values + */ + private static Map appendDisplayNames(final Calendar calendar, final Locale locale, final int field, final StringBuilder regex) { + Objects.requireNonNull(calendar, "calendar"); + final Map values = new HashMap<>(); + final Locale actualLocale = LocaleUtils.toLocale(locale); + final Map displayNames = calendar.getDisplayNames(field, Calendar.ALL_STYLES, actualLocale); + final TreeSet sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE); + displayNames.forEach((k, v) -> { + final String keyLc = k.toLowerCase(actualLocale); + if (sorted.add(keyLc)) { + values.put(keyLc, v); + } + }); + sorted.forEach(symbol -> simpleQuote(regex, symbol).append('|')); + return values; + } + + /** + * Gets a cache of Strategies for a particular field + * + * @param field The Calendar field + * @return a cache of Locale to Strategy + */ + private static ConcurrentMap getCache(final int field) { + synchronized (caches) { + if (caches[field] == null) { + caches[field] = new ConcurrentHashMap<>(3); + } + return caches[field]; + } + } + + private static boolean isFormatLetter(final char c) { + return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; + } + + private static StringBuilder simpleQuote(final StringBuilder sb, final String value) { + for (int i = 0; i < value.length(); ++i) { + final char c = value.charAt(i); + switch (c) { + case '\\': + case '^': + case '$': + case '.': + case '|': + case '?': + case '*': + case '+': + case '(': + case ')': + case '[': + case '{': + sb.append('\\'); + default: + sb.append(c); + } + } + if (sb.charAt(sb.length() - 1) == '.') { + // trailing '.' is optional + sb.append('?'); + } + return sb; + } + + /** Input pattern. */ + private final String pattern; + + /** Input TimeZone. */ + private final TimeZone timeZone; + + /** Input Locale. */ + private final Locale locale; + + /** + * Century from Date. + */ + private final int century; + + /** + * Start year from Date. + */ + private final int startYear; + + /** Initialized from Calendar. */ + private transient List patterns; + + /** + * Constructs a new FastDateParser. + * + * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the factory methods of {@link FastDateFormat} to get a cached + * FastDateParser instance. + * + * @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale + */ + protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) { + this(pattern, timeZone, locale, null); + } + + /** + * Constructs a new FastDateParser. + * + * @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale locale, null maps to the default Locale. + * @param centuryStart The start of the century for 2 digit year parsing + * + * @since 3.5 + */ + protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { + this.pattern = Objects.requireNonNull(pattern, "pattern"); + this.timeZone = Objects.requireNonNull(timeZone, "timeZone"); + this.locale = LocaleUtils.toLocale(locale); + + final Calendar definingCalendar = Calendar.getInstance(timeZone, this.locale); + + final int centuryStartYear; + if (centuryStart != null) { + definingCalendar.setTime(centuryStart); + centuryStartYear = definingCalendar.get(Calendar.YEAR); + } else if (this.locale.equals(JAPANESE_IMPERIAL)) { + centuryStartYear = 0; + } else { + // from 80 years ago to 20 years from now + definingCalendar.setTime(new Date()); + centuryStartYear = definingCalendar.get(Calendar.YEAR) - 80; + } + century = centuryStartYear / 100 * 100; + startYear = centuryStartYear - century; + + init(definingCalendar); + } + + /** + * Adjusts dates to be within appropriate century + * + * @param twoDigitYear The year to adjust + * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive) + */ + private int adjustYear(final int twoDigitYear) { + final int trial = century + twoDigitYear; + return twoDigitYear >= startYear ? trial : trial + 100; + } + + // Basics + /** + * Compares another object for equality with this object. + * + * @param obj the object to compare to + * @return {@code true}if equal to this instance + */ + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof FastDateParser)) { + return false; + } + final FastDateParser other = (FastDateParser) obj; + return pattern.equals(other.pattern) && timeZone.equals(other.timeZone) && locale.equals(other.locale); + } + + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#getLocale() + */ + @Override + public Locale getLocale() { + return locale; + } + + /** + * Constructs a Strategy that parses a Text field + * + * @param field The Calendar field + * @param definingCalendar The calendar to obtain the short and long values + * @return a TextStrategy for the field and Locale + */ + private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) { + final ConcurrentMap cache = getCache(field); + return cache.computeIfAbsent(locale, + k -> field == Calendar.ZONE_OFFSET ? new TimeZoneStrategy(locale) : new CaseInsensitiveTextStrategy(field, definingCalendar, locale)); + } + // Accessors + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#getPattern() + */ + @Override + public String getPattern() { + return pattern; + } + /** + * Gets a Strategy given a field from a SimpleDateFormat pattern + * + * @param f A sub-sequence of the SimpleDateFormat pattern + * @param width formatting width + * @param definingCalendar The calendar to obtain the short and long values + * @return The Strategy that will handle parsing for the field + */ + private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) { + switch (f) { + default: + throw new IllegalArgumentException("Format '" + f + "' not supported"); + case 'D': + return DAY_OF_YEAR_STRATEGY; + case 'E': + return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar); + case 'F': + return DAY_OF_WEEK_IN_MONTH_STRATEGY; + case 'G': + return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar); + case 'H': // Hour in day (0-23) + return HOUR_OF_DAY_STRATEGY; + case 'K': // Hour in am/pm (0-11) + return HOUR_STRATEGY; + case 'M': + case 'L': + return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY; + case 'S': + return MILLISECOND_STRATEGY; + case 'W': + return WEEK_OF_MONTH_STRATEGY; + case 'a': + return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar); + case 'd': + return DAY_OF_MONTH_STRATEGY; + case 'h': // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0 + return HOUR12_STRATEGY; + case 'k': // Hour in day (1-24), i.e. midnight is 24, not 0 + return HOUR24_OF_DAY_STRATEGY; + case 'm': + return MINUTE_STRATEGY; + case 's': + return SECOND_STRATEGY; + case 'u': + return DAY_OF_WEEK_STRATEGY; + case 'w': + return WEEK_OF_YEAR_STRATEGY; + case 'y': + case 'Y': + return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY; + case 'X': + return ISO8601TimeZoneStrategy.getStrategy(width); + case 'Z': + if (width == 2) { + return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY; + } + //$FALL-THROUGH$ + case 'z': + return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar); + } + } + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#getTimeZone() + */ + @Override + public TimeZone getTimeZone() { + return timeZone; + } + /** + * Returns a hash code compatible with equals. + * + * @return a hash code compatible with equals + */ + @Override + public int hashCode() { + return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode()); + } + /** + * Initializes derived fields from defining fields. This is called from constructor and from readObject (de-serialization) + * + * @param definingCalendar the {@link java.util.Calendar} instance used to initialize this FastDateParser + */ + private void init(final Calendar definingCalendar) { + patterns = new ArrayList<>(); + + final StrategyParser strategyParser = new StrategyParser(definingCalendar); + for (;;) { + final StrategyAndWidth field = strategyParser.getNextStrategy(); + if (field == null) { + break; + } + patterns.add(field); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#parse(String) + */ + @Override + public Date parse(final String source) throws ParseException { + final ParsePosition pp = new ParsePosition(0); + final Date date = parse(source, pp); + if (date == null) { + // Add a note regarding supported date range + if (locale.equals(JAPANESE_IMPERIAL)) { + throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\nUnparseable date: \"" + source, + pp.getErrorIndex()); + } + throw new ParseException("Unparseable date: " + source, pp.getErrorIndex()); + } + return date; + } + /** + * This implementation updates the ParsePosition if the parse succeeds. However, it sets the error index to the position before the failed field unlike the + * method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets the error index to after the failed field. + *

+ * To determine if the parse has succeeded, the caller must check if the current parse position given by {@link ParsePosition#getIndex()} has been updated. + * If the input buffer has been fully parsed, then the index will point to just after the end of the input buffer. + * + * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition) + */ + @Override + public Date parse(final String source, final ParsePosition pos) { + // timing tests indicate getting new instance is 19% faster than cloning + final Calendar cal = Calendar.getInstance(timeZone, locale); + cal.clear(); + + return parse(source, pos, cal) ? cal.getTime() : null; + } + /** + * Parses a formatted date string according to the format. Updates the Calendar with parsed fields. Upon success, the ParsePosition index is updated to + * indicate how much of the source text was consumed. Not all source text needs to be consumed. Upon parse failure, ParsePosition error index is updated to + * the offset of the source text which does not match the supplied format. + * + * @param source The text to parse. + * @param pos On input, the position in the source to start parsing, on output, updated position. + * @param calendar The calendar into which to set parsed fields. + * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated) + * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is out of range. + */ + @Override + public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) { + final ListIterator lt = patterns.listIterator(); + while (lt.hasNext()) { + final StrategyAndWidth strategyAndWidth = lt.next(); + final int maxWidth = strategyAndWidth.getMaxWidth(lt); + if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) { + return false; + } + } + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#parseObject(String) + */ + @Override + public Object parseObject(final String source) throws ParseException { + return parse(source); + } + + /* + * (non-Javadoc) + * + * @see org.apache.commons.lang3.time.DateParser#parseObject(String, java.text.ParsePosition) + */ + @Override + public Object parseObject(final String source, final ParsePosition pos) { + return parse(source, pos); + } + // Serializing + /** + * Creates 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(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); + init(definingCalendar); + } + /** + * Gets a string version of this formatter. + * + * @return a debugging string + */ + @Override + public String toString() { + return "FastDateParser[" + pattern + ", " + locale + ", " + timeZone.getID() + "]"; + } + /** + * Converts all state of this instance to a String handy for debugging. + * + * @return a string. + * @since 3.12.0 + */ + public String toStringAll() { + return "FastDateParser [pattern=" + pattern + ", timeZone=" + timeZone + ", locale=" + locale + ", century=" + century + ", startYear=" + startYear + + ", patterns=" + patterns + "]"; + } } diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java index eafc6535a..8431251af 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java @@ -93,6 +93,782 @@ public class FastDatePrinter implements DatePrinter, Serializable { // taking the value and adding (mathematically) the ASCII value for '0'. // So, don't change this code! It works and is very fast. + /** + * Inner class to output a constant single character. + */ + private static class CharacterLiteral implements Rule { + private final char value; + + /** + * Constructs a new instance of {@link CharacterLiteral} + * to hold the specified value. + * + * @param value the character literal + */ + CharacterLiteral(final char value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + buffer.append(value); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 1; + } + } + + /** + * Inner class to output the numeric day in week. + */ + private static class DayInWeekField implements NumberRule { + private final NumberRule rule; + + DayInWeekField(final NumberRule rule) { + this.rule = rule; + } + + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + final int value = calendar.get(Calendar.DAY_OF_WEEK); + rule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1); + } + + @Override + public void appendTo(final Appendable buffer, final int value) throws IOException { + rule.appendTo(buffer, value); + } + + @Override + public int estimateLength() { + return rule.estimateLength(); + } + } + + /** + * Inner class to output a time zone as a number {@code +/-HHMM} + * or {@code +/-HH:MM}. + */ + private static class Iso8601_Rule implements Rule { + + // Sign TwoDigitHours or Z + static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); + // Sign TwoDigitHours Minutes or Z + static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); + // Sign TwoDigitHours : Minutes or Z + static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6); + + /** + * Factory method for Iso8601_Rules. + * + * @param tokenLen a token indicating the length of the TimeZone String to be formatted. + * @return an Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such + * rule exists, an IllegalArgumentException will be thrown. + */ + static Iso8601_Rule getRule(final int tokenLen) { + switch (tokenLen) { + case 1: + return ISO8601_HOURS; + case 2: + return ISO8601_HOURS_MINUTES; + case 3: + return ISO8601_HOURS_COLON_MINUTES; + default: + throw new IllegalArgumentException("invalid number of X"); + } + } + + private final int length; + + /** + * Constructs an instance of {@code Iso8601_Rule} with the specified properties. + * + * @param length The number of characters in output (unless Z is output) + */ + Iso8601_Rule(final int length) { + this.length = length; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + if (offset == 0) { + buffer.append("Z"); + return; + } + + if (offset < 0) { + buffer.append('-'); + offset = -offset; + } else { + buffer.append('+'); + } + + final int hours = offset / (60 * 60 * 1000); + appendDigits(buffer, hours); + + if (length<5) { + return; + } + + if (length==6) { + buffer.append(':'); + } + + final int minutes = offset / (60 * 1000) - 60 * hours; + appendDigits(buffer, minutes); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return length; + } + } + /** + * 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 + * @throws IOException if an I/O error occurs. + */ + void appendTo(Appendable buffer, int value) throws IOException; + } + /** + * Inner class to output a padded number. + */ + private static class PaddedNumberField implements NumberRule { + private final int field; + private final int size; + + /** + * Constructs an instance of {@link PaddedNumberField}. + * + * @param field the field + * @param size size of the output field + */ + PaddedNumberField(final int field, final int size) { + if (size < 3) { + // Should use UnpaddedNumberField or TwoDigitNumberField. + throw new IllegalArgumentException(); + } + this.field = field; + this.size = size; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(field)); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + appendFullDigits(buffer, value, size); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return size; + } + } + // Rules + /** + * Inner class defining a rule. + */ + private interface Rule { + /** + * Appends the value of the specified calendar to the output buffer based on the rule implementation. + * + * @param buf the output buffer + * @param calendar calendar to be appended + * @throws IOException if an I/O error occurs. + */ + void appendTo(Appendable buf, Calendar calendar) throws IOException; + + /** + * Returns the estimated length of the result. + * + * @return the estimated length + */ + int estimateLength(); + } + + /** + * Inner class to output a constant string. + */ + private static class StringLiteral implements Rule { + private final String value; + + /** + * Constructs a new instance of {@link StringLiteral} + * to hold the specified value. + * + * @param value the string literal + */ + StringLiteral(final String value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + buffer.append(value); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return value.length(); + } + } + /** + * Inner class to output one of a set of values. + */ + private static class TextField implements Rule { + private final int field; + private final String[] values; + + /** + * Constructs an instance of {@link TextField} + * with the specified field and values. + * + * @param field the field + * @param values the field values + */ + TextField(final int field, final String[] values) { + this.field = field; + this.values = values; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + buffer.append(values[calendar.get(field)]); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + int max = 0; + for (int i=values.length; --i >= 0; ) { + final int len = values[i].length(); + if (len > max) { + max = len; + } + } + return max; + } + } + /** + * Inner class that acts as a compound key for time zone names. + */ + private static class TimeZoneDisplayKey { + private final TimeZone timeZone; + private final int style; + private final Locale locale; + + /** + * Constructs an instance of {@link 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 time zone style + * @param locale the time zone locale + */ + TimeZoneDisplayKey(final TimeZone timeZone, + final boolean daylight, final int style, final Locale locale) { + this.timeZone = timeZone; + if (daylight) { + this.style = style | 0x80000000; + } else { + this.style = style; + } + this.locale = LocaleUtils.toLocale(locale); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TimeZoneDisplayKey) { + final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj; + return + timeZone.equals(other.timeZone) && + style == other.style && + locale.equals(other.locale); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return (style * 31 + locale.hashCode() ) * 31 + timeZone.hashCode(); + } + } + /** + * Inner class to output a time zone name. + */ + private static class TimeZoneNameRule implements Rule { + private final Locale locale; + private final int style; + private final String standard; + private final String daylight; + + /** + * Constructs an instance of {@link TimeZoneNameRule} with the specified properties. + * + * @param timeZone the time zone + * @param locale the locale + * @param style the style + */ + TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { + this.locale = LocaleUtils.toLocale(locale); + this.style = style; + this.standard = getTimeZoneDisplay(timeZone, false, style, locale); + this.daylight = getTimeZoneDisplay(timeZone, true, style, locale); + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + final TimeZone zone = calendar.getTimeZone(); + if (calendar.get(Calendar.DST_OFFSET) == 0) { + buffer.append(getTimeZoneDisplay(zone, false, style, locale)); + } else { + buffer.append(getTimeZoneDisplay(zone, true, style, locale)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + // We have no access to the Calendar object that will be passed to + // appendTo so base estimate on the TimeZone passed to the + // constructor + return Math.max(standard.length(), daylight.length()); + } + } + /** + * 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); + + private final boolean colon; + + /** + * Constructs an instance of {@link TimeZoneNumberRule} with the specified properties. + * + * @param colon add colon between HH and MM in the output if {@code true} + */ + TimeZoneNumberRule(final boolean colon) { + this.colon = colon; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + + int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + if (offset < 0) { + buffer.append('-'); + offset = -offset; + } else { + buffer.append('+'); + } + + final int hours = offset / (60 * 60 * 1000); + appendDigits(buffer, hours); + + if (colon) { + buffer.append(':'); + } + + final int minutes = offset / (60 * 1000) - 60 * hours; + appendDigits(buffer, minutes); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 5; + } + } + + /** + * Inner class to output the twelve hour field. + */ + private static class TwelveHourField implements NumberRule { + private final NumberRule rule; + + /** + * Constructs an instance of {@link TwelveHourField} with the specified + * {@link NumberRule}. + * + * @param rule the rule + */ + TwelveHourField(final NumberRule rule) { + this.rule = rule; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + int value = calendar.get(Calendar.HOUR); + if (value == 0) { + value = calendar.getLeastMaximum(Calendar.HOUR) + 1; + } + rule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final int value) throws IOException { + rule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return rule.estimateLength(); + } + } + + /** + * Inner class to output the twenty four hour field. + */ + private static class TwentyFourHourField implements NumberRule { + private final NumberRule rule; + + /** + * Constructs an instance of {@link TwentyFourHourField} with the specified + * {@link NumberRule}. + * + * @param rule the rule + */ + TwentyFourHourField(final NumberRule rule) { + this.rule = rule; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + int value = calendar.get(Calendar.HOUR_OF_DAY); + if (value == 0) { + value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; + } + rule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final int value) throws IOException { + rule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return rule.estimateLength(); + } + } + + /** + * Inner class to output a two digit month. + */ + private static class TwoDigitMonthField implements NumberRule { + static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); + + /** + * Constructs an instance of {@link TwoDigitMonthField}. + */ + TwoDigitMonthField() { + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(Calendar.MONTH) + 1); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + appendDigits(buffer, value); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 2; + } + } + + /** + * Inner class to output a two digit number. + */ + private static class TwoDigitNumberField implements NumberRule { + private final int field; + + /** + * Constructs an instance of {@link TwoDigitNumberField} with the specified field. + * + * @param field the field + */ + TwoDigitNumberField(final int field) { + this.field = field; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(field)); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + if (value < 100) { + appendDigits(buffer, value); + } else { + appendFullDigits(buffer, value, 2); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 2; + } + } + + /** + * Inner class to output a two digit year. + */ + private static class TwoDigitYearField implements NumberRule { + static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); + + /** + * Constructs an instance of {@link TwoDigitYearField}. + */ + TwoDigitYearField() { + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(Calendar.YEAR) % 100); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + appendDigits(buffer, value % 100); + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 2; + } + } + + /** + * Inner class to output an unpadded month. + */ + private static class UnpaddedMonthField implements NumberRule { + static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); + + /** + * Constructs an instance of {@link UnpaddedMonthField}. + * + */ + UnpaddedMonthField() { + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(Calendar.MONTH) + 1); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + if (value < 10) { + buffer.append((char) (value + '0')); + } else { + appendDigits(buffer, value); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 2; + } + } + + /** + * Inner class to output an unpadded number. + */ + private static class UnpaddedNumberField implements NumberRule { + private final int field; + + /** + * Constructs an instance of {@link UnpaddedNumberField} with the specified field. + * + * @param field the field + */ + UnpaddedNumberField(final int field) { + this.field = field; + } + + /** + * {@inheritDoc} + */ + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + appendTo(buffer, calendar.get(field)); + } + + /** + * {@inheritDoc} + */ + @Override + public final void appendTo(final Appendable buffer, final int value) throws IOException { + if (value < 10) { + buffer.append((char) (value + '0')); + } else if (value < 100) { + appendDigits(buffer, value); + } else { + appendFullDigits(buffer, value, 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int estimateLength() { + return 4; + } + } + + /** + * Inner class to output the numeric day in week. + */ + private static class WeekYear implements NumberRule { + private final NumberRule rule; + + WeekYear(final NumberRule rule) { + this.rule = rule; + } + + @Override + public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { + rule.appendTo(buffer, calendar.getWeekYear()); + } + + @Override + public void appendTo(final Appendable buffer, final int value) throws IOException { + rule.appendTo(buffer, value); + } + + @Override + public int estimateLength() { + return rule.estimateLength(); + } + } + /** Empty array. */ private static final Rule[] EMPTY_RULE_ARRAY = {}; @@ -107,35 +883,148 @@ public class FastDatePrinter implements DatePrinter, Serializable { * 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 int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3 + + private static final ConcurrentMap cTimeZoneDisplayCache = + new ConcurrentHashMap<>(7); + + /** + * Appends two digits to the given buffer. + * + * @param buffer the buffer to append to. + * @param value the value to append digits from. + * @throws IOException If an I/O error occurs + */ + private static void appendDigits(final Appendable buffer, final int value) throws IOException { + buffer.append((char) (value / 10 + '0')); + buffer.append((char) (value % 10 + '0')); + } + + /** + * Appends all digits to the given buffer. + * + * @param buffer the buffer to append to. + * @param value the value to append digits from. + * @param minFieldWidth Minimum field width. + * @throws IOException If an I/O error occurs + */ + private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException { + // specialized paths for 1 to 4 digits -> avoid the memory allocation from the temporary work array + // see LANG-1248 + if (value < 10000) { + // less memory allocation path works for four digits or less + + int nDigits = 4; + if (value < 1000) { + --nDigits; + if (value < 100) { + --nDigits; + if (value < 10) { + --nDigits; + } + } + } + // left zero pad + for (int i = minFieldWidth - nDigits; i > 0; --i) { + buffer.append('0'); + } + + switch (nDigits) { + case 4: + buffer.append((char) (value / 1000 + '0')); + value %= 1000; + case 3: + if (value >= 100) { + buffer.append((char) (value / 100 + '0')); + value %= 100; + } else { + buffer.append('0'); + } + case 2: + if (value >= 10) { + buffer.append((char) (value / 10 + '0')); + value %= 10; + } else { + buffer.append('0'); + } + case 1: + buffer.append((char) (value + '0')); + } + } else { + // more memory allocation path works for any digits + + // build up decimal representation in reverse + final char[] work = new char[MAX_DIGITS]; + int digit = 0; + while (value != 0) { + work[digit++] = (char) (value % 10 + '0'); + value = value / 10; + } + + // pad with zeros + while (digit < minFieldWidth) { + buffer.append('0'); + --minFieldWidth; + } + + // reverse + while (--digit >= 0) { + buffer.append(work[digit]); + } + } + } + + /** + * 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(final TimeZone tz, final boolean daylight, final int style, final Locale locale) { + final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); + // This is a very slow call, so cache the results. + return cTimeZoneDisplayCache.computeIfAbsent(key, k -> tz.getDisplayName(daylight, style, locale)); + } + /** * The pattern. */ private final String pattern; + /** * The time zone. */ private final TimeZone timeZone; + /** * The locale. */ private final Locale locale; + /** * The parsed rules. */ private transient Rule[] rules; + /** * The estimated maximum length. */ @@ -159,6 +1048,251 @@ public class FastDatePrinter implements DatePrinter, Serializable { init(); } + /** + * Performs the formatting by applying the rules to the + * specified calendar. + * + * @param calendar the calendar to format + * @param buf the buffer to format into + * @param the Appendable class type, usually StringBuilder or StringBuffer. + * @return the specified string buffer + */ + private B applyRules(final Calendar calendar, final B buf) { + try { + for (final Rule rule : rules) { + rule.appendTo(buf, calendar); + } + } catch (final IOException ioe) { + ExceptionUtils.asRuntimeException(ioe); + } + return 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 + * + * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)} + */ + @Deprecated + protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { + return (StringBuffer) applyRules(calendar, (Appendable) buf); + } + + /** + * Creates a String representation of the given Calendar by applying the rules of this printer to it. + * @param c the Calendar to apply the rules to. + * @return a String representation of the given Calendar. + */ + private String applyRulesToString(final Calendar c) { + return applyRules(c, new StringBuilder(maxLengthEstimate)).toString(); + } + + // Basics + /** + * Compares two objects for equality. + * + * @param obj the object to compare to + * @return {@code true} if equal + */ + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof FastDatePrinter)) { + return false; + } + final FastDatePrinter other = (FastDatePrinter) obj; + return pattern.equals(other.pattern) + && timeZone.equals(other.timeZone) + && locale.equals(other.locale); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar) + */ + @Override + public String format(final Calendar calendar) { + return format(calendar, new StringBuilder(maxLengthEstimate)).toString(); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, Appendable) + */ + @Override + public B format(Calendar calendar, final B buf) { + // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored + if (!calendar.getTimeZone().equals(timeZone)) { + calendar = (Calendar) calendar.clone(); + calendar.setTimeZone(timeZone); + } + return applyRules(calendar, buf); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, StringBuffer) + */ + @Override + public StringBuffer format(final Calendar calendar, final StringBuffer buf) { + // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored + return format(calendar.getTime(), buf); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date) + */ + @Override + public String format(final Date date) { + final Calendar c = newCalendar(); + c.setTime(date); + return applyRulesToString(c); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, Appendable) + */ + @Override + public B format(final Date date, final B buf) { + final Calendar c = newCalendar(); + c.setTime(date); + return applyRules(c, buf); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, StringBuffer) + */ + @Override + public StringBuffer format(final Date date, final StringBuffer buf) { + final Calendar c = newCalendar(); + c.setTime(date); + return (StringBuffer) applyRules(c, (Appendable) buf); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(long) + */ + @Override + public String format(final long millis) { + final Calendar c = newCalendar(); + c.setTimeInMillis(millis); + return applyRulesToString(c); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(long, Appendable) + */ + @Override + public B format(final long millis, final B buf) { + final Calendar c = newCalendar(); + c.setTimeInMillis(millis); + return applyRules(c, buf); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#format(long, StringBuffer) + */ + @Override + public StringBuffer format(final long millis, final StringBuffer buf) { + final Calendar c = newCalendar(); + c.setTimeInMillis(millis); + return (StringBuffer) applyRules(c, (Appendable) buf); + } + + /** + * Formats a {@link Date}, {@link Calendar} or + * {@link Long} (milliseconds) object. + * @since 3.5 + * @param obj the object to format + * @return The formatted value. + */ + String format(final Object obj) { + if (obj instanceof Date) { + return format((Date) obj); + } + if (obj instanceof Calendar) { + return format((Calendar) obj); + } + if (obj instanceof Long) { + return format(((Long) obj).longValue()); + } + throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "")); + } + + // Format methods + /** + * Formats a {@link Date}, {@link Calendar} or + * {@link Long} (milliseconds) object. + * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}. + * @param obj the object to format + * @param toAppendTo the buffer to append to + * @param pos the position - ignored + * @return the buffer passed in + */ + @Deprecated + @Override + public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { + if (obj instanceof Date) { + return format((Date) obj, toAppendTo); + } + if (obj instanceof Calendar) { + return format((Calendar) obj, toAppendTo); + } + if (obj instanceof Long) { + return format(((Long) obj).longValue(), toAppendTo); + } + throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "")); + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#getLocale() + */ + @Override + public Locale getLocale() { + return locale; + } + + /** + * 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 maxLengthEstimate; + } + + // Accessors + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#getPattern() + */ + @Override + public String getPattern() { + return pattern; + } + + /* (non-Javadoc) + * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone() + */ + @Override + public TimeZone getTimeZone() { + return timeZone; + } + + /** + * Returns a hash code compatible with equals. + * + * @return a hash code compatible with equals + */ + @Override + public int hashCode() { + return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode()); + } + /** * Initializes the instance for first use. */ @@ -174,6 +1308,15 @@ public class FastDatePrinter implements DatePrinter, Serializable { maxLengthEstimate = len; } + + /** + * Creates a new Calendar instance. + * @return a new Calendar instance. + */ + private Calendar newCalendar() { + return Calendar.getInstance(timeZone, locale); + } + // Parse the pattern /** * Returns a list of Rules given a pattern. @@ -385,6 +1528,20 @@ public class FastDatePrinter implements DatePrinter, Serializable { return buf.toString(); } + // 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(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + init(); + } + /** * Gets an appropriate rule for the padding required. * @@ -403,259 +1560,6 @@ public class FastDatePrinter implements DatePrinter, Serializable { } } - // Format methods - /** - * Formats a {@link Date}, {@link Calendar} or - * {@link Long} (milliseconds) object. - * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}. - * @param obj the object to format - * @param toAppendTo the buffer to append to - * @param pos the position - ignored - * @return the buffer passed in - */ - @Deprecated - @Override - public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { - if (obj instanceof Date) { - return format((Date) obj, toAppendTo); - } - if (obj instanceof Calendar) { - return format((Calendar) obj, toAppendTo); - } - if (obj instanceof Long) { - return format(((Long) obj).longValue(), toAppendTo); - } - throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "")); - } - - /** - * Formats a {@link Date}, {@link Calendar} or - * {@link Long} (milliseconds) object. - * @since 3.5 - * @param obj the object to format - * @return The formatted value. - */ - String format(final Object obj) { - if (obj instanceof Date) { - return format((Date) obj); - } - if (obj instanceof Calendar) { - return format((Calendar) obj); - } - if (obj instanceof Long) { - return format(((Long) obj).longValue()); - } - throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "")); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(long) - */ - @Override - public String format(final long millis) { - final Calendar c = newCalendar(); - c.setTimeInMillis(millis); - return applyRulesToString(c); - } - - /** - * Creates a String representation of the given Calendar by applying the rules of this printer to it. - * @param c the Calendar to apply the rules to. - * @return a String representation of the given Calendar. - */ - private String applyRulesToString(final Calendar c) { - return applyRules(c, new StringBuilder(maxLengthEstimate)).toString(); - } - - /** - * Creates a new Calendar instance. - * @return a new Calendar instance. - */ - private Calendar newCalendar() { - return Calendar.getInstance(timeZone, locale); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date) - */ - @Override - public String format(final Date date) { - final Calendar c = newCalendar(); - c.setTime(date); - return applyRulesToString(c); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar) - */ - @Override - public String format(final Calendar calendar) { - return format(calendar, new StringBuilder(maxLengthEstimate)).toString(); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(long, StringBuffer) - */ - @Override - public StringBuffer format(final long millis, final StringBuffer buf) { - final Calendar c = newCalendar(); - c.setTimeInMillis(millis); - return (StringBuffer) applyRules(c, (Appendable) buf); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, StringBuffer) - */ - @Override - public StringBuffer format(final Date date, final StringBuffer buf) { - final Calendar c = newCalendar(); - c.setTime(date); - return (StringBuffer) applyRules(c, (Appendable) buf); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, StringBuffer) - */ - @Override - public StringBuffer format(final Calendar calendar, final StringBuffer buf) { - // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored - return format(calendar.getTime(), buf); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(long, Appendable) - */ - @Override - public B format(final long millis, final B buf) { - final Calendar c = newCalendar(); - c.setTimeInMillis(millis); - return applyRules(c, buf); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, Appendable) - */ - @Override - public B format(final Date date, final B buf) { - final Calendar c = newCalendar(); - c.setTime(date); - return applyRules(c, buf); - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, Appendable) - */ - @Override - public B format(Calendar calendar, final B buf) { - // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored - if (!calendar.getTimeZone().equals(timeZone)) { - calendar = (Calendar) calendar.clone(); - calendar.setTimeZone(timeZone); - } - 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 - * - * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)} - */ - @Deprecated - protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { - return (StringBuffer) applyRules(calendar, (Appendable) 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 - * @param the Appendable class type, usually StringBuilder or StringBuffer. - * @return the specified string buffer - */ - private B applyRules(final Calendar calendar, final B buf) { - try { - for (final Rule rule : rules) { - rule.appendTo(buf, calendar); - } - } catch (final IOException ioe) { - ExceptionUtils.asRuntimeException(ioe); - } - return buf; - } - - // Accessors - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#getPattern() - */ - @Override - public String getPattern() { - return pattern; - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone() - */ - @Override - public TimeZone getTimeZone() { - return timeZone; - } - - /* (non-Javadoc) - * @see org.apache.commons.lang3.time.DatePrinter#getLocale() - */ - @Override - public Locale getLocale() { - return locale; - } - - /** - * 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 maxLengthEstimate; - } - - // Basics - /** - * Compares two objects for equality. - * - * @param obj the object to compare to - * @return {@code true} if equal - */ - @Override - public boolean equals(final Object obj) { - if (!(obj instanceof FastDatePrinter)) { - return false; - } - final FastDatePrinter other = (FastDatePrinter) obj; - return pattern.equals(other.pattern) - && timeZone.equals(other.timeZone) - && locale.equals(other.locale); - } - - /** - * Returns a hash code compatible with equals. - * - * @return a hash code compatible with equals - */ - @Override - public int hashCode() { - return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode()); - } - /** * Gets a debugging string version of this formatter. * @@ -665,908 +1569,4 @@ public class FastDatePrinter implements DatePrinter, Serializable { public String toString() { return "FastDatePrinter[" + pattern + "," + locale + "," + timeZone.getID() + "]"; } - - // 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(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - init(); - } - - /** - * Appends two digits to the given buffer. - * - * @param buffer the buffer to append to. - * @param value the value to append digits from. - * @throws IOException If an I/O error occurs - */ - private static void appendDigits(final Appendable buffer, final int value) throws IOException { - buffer.append((char) (value / 10 + '0')); - buffer.append((char) (value % 10 + '0')); - } - - private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3 - - /** - * Appends all digits to the given buffer. - * - * @param buffer the buffer to append to. - * @param value the value to append digits from. - * @param minFieldWidth Minimum field width. - * @throws IOException If an I/O error occurs - */ - private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException { - // specialized paths for 1 to 4 digits -> avoid the memory allocation from the temporary work array - // see LANG-1248 - if (value < 10000) { - // less memory allocation path works for four digits or less - - int nDigits = 4; - if (value < 1000) { - --nDigits; - if (value < 100) { - --nDigits; - if (value < 10) { - --nDigits; - } - } - } - // left zero pad - for (int i = minFieldWidth - nDigits; i > 0; --i) { - buffer.append('0'); - } - - switch (nDigits) { - case 4: - buffer.append((char) (value / 1000 + '0')); - value %= 1000; - case 3: - if (value >= 100) { - buffer.append((char) (value / 100 + '0')); - value %= 100; - } else { - buffer.append('0'); - } - case 2: - if (value >= 10) { - buffer.append((char) (value / 10 + '0')); - value %= 10; - } else { - buffer.append('0'); - } - case 1: - buffer.append((char) (value + '0')); - } - } else { - // more memory allocation path works for any digits - - // build up decimal representation in reverse - final char[] work = new char[MAX_DIGITS]; - int digit = 0; - while (value != 0) { - work[digit++] = (char) (value % 10 + '0'); - value = value / 10; - } - - // pad with zeros - while (digit < minFieldWidth) { - buffer.append('0'); - --minFieldWidth; - } - - // reverse - while (--digit >= 0) { - buffer.append(work[digit]); - } - } - } - - // Rules - /** - * Inner class defining a rule. - */ - private interface Rule { - /** - * Returns the estimated length 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 buf the output buffer - * @param calendar calendar to be appended - * @throws IOException if an I/O error occurs. - */ - void appendTo(Appendable buf, Calendar calendar) throws IOException; - } - - /** - * 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 - * @throws IOException if an I/O error occurs. - */ - void appendTo(Appendable buffer, int value) throws IOException; - } - - /** - * Inner class to output a constant single character. - */ - private static class CharacterLiteral implements Rule { - private final char value; - - /** - * Constructs a new instance of {@link CharacterLiteral} - * to hold the specified value. - * - * @param value the character literal - */ - CharacterLiteral(final char value) { - this.value = value; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 1; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - buffer.append(value); - } - } - - /** - * Inner class to output a constant string. - */ - private static class StringLiteral implements Rule { - private final String value; - - /** - * Constructs a new instance of {@link StringLiteral} - * to hold the specified value. - * - * @param value the string literal - */ - StringLiteral(final String value) { - this.value = value; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return value.length(); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - buffer.append(value); - } - } - - /** - * Inner class to output one of a set of values. - */ - private static class TextField implements Rule { - private final int field; - private final String[] values; - - /** - * Constructs an instance of {@link TextField} - * with the specified field and values. - * - * @param field the field - * @param values the field values - */ - TextField(final int field, final String[] values) { - this.field = field; - this.values = values; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - int max = 0; - for (int i=values.length; --i >= 0; ) { - final int len = values[i].length(); - if (len > max) { - max = len; - } - } - return max; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - buffer.append(values[calendar.get(field)]); - } - } - - /** - * Inner class to output an unpadded number. - */ - private static class UnpaddedNumberField implements NumberRule { - private final int field; - - /** - * Constructs an instance of {@link UnpaddedNumberField} with the specified field. - * - * @param field the field - */ - UnpaddedNumberField(final int field) { - this.field = field; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 4; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(field)); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - if (value < 10) { - buffer.append((char) (value + '0')); - } else if (value < 100) { - appendDigits(buffer, value); - } else { - appendFullDigits(buffer, value, 1); - } - } - } - - /** - * Inner class to output an unpadded month. - */ - private static class UnpaddedMonthField implements NumberRule { - static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); - - /** - * Constructs an instance of {@link UnpaddedMonthField}. - * - */ - UnpaddedMonthField() { - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 2; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(Calendar.MONTH) + 1); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - if (value < 10) { - buffer.append((char) (value + '0')); - } else { - appendDigits(buffer, value); - } - } - } - - /** - * Inner class to output a padded number. - */ - private static class PaddedNumberField implements NumberRule { - private final int field; - private final int size; - - /** - * Constructs an instance of {@link PaddedNumberField}. - * - * @param field the field - * @param size size of the output field - */ - PaddedNumberField(final int field, final int size) { - if (size < 3) { - // Should use UnpaddedNumberField or TwoDigitNumberField. - throw new IllegalArgumentException(); - } - this.field = field; - this.size = size; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return size; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(field)); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - appendFullDigits(buffer, value, size); - } - } - - /** - * Inner class to output a two digit number. - */ - private static class TwoDigitNumberField implements NumberRule { - private final int field; - - /** - * Constructs an instance of {@link TwoDigitNumberField} with the specified field. - * - * @param field the field - */ - TwoDigitNumberField(final int field) { - this.field = field; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 2; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(field)); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - if (value < 100) { - appendDigits(buffer, value); - } else { - appendFullDigits(buffer, value, 2); - } - } - } - - /** - * Inner class to output a two digit year. - */ - private static class TwoDigitYearField implements NumberRule { - static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); - - /** - * Constructs an instance of {@link TwoDigitYearField}. - */ - TwoDigitYearField() { - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 2; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(Calendar.YEAR) % 100); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - appendDigits(buffer, value % 100); - } - } - - /** - * Inner class to output a two digit month. - */ - private static class TwoDigitMonthField implements NumberRule { - static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); - - /** - * Constructs an instance of {@link TwoDigitMonthField}. - */ - TwoDigitMonthField() { - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 2; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - appendTo(buffer, calendar.get(Calendar.MONTH) + 1); - } - - /** - * {@inheritDoc} - */ - @Override - public final void appendTo(final Appendable buffer, final int value) throws IOException { - appendDigits(buffer, value); - } - } - - /** - * Inner class to output the twelve hour field. - */ - private static class TwelveHourField implements NumberRule { - private final NumberRule rule; - - /** - * Constructs an instance of {@link TwelveHourField} with the specified - * {@link NumberRule}. - * - * @param rule the rule - */ - TwelveHourField(final NumberRule rule) { - this.rule = rule; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return rule.estimateLength(); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - int value = calendar.get(Calendar.HOUR); - if (value == 0) { - value = calendar.getLeastMaximum(Calendar.HOUR) + 1; - } - rule.appendTo(buffer, value); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final int value) throws IOException { - rule.appendTo(buffer, value); - } - } - - /** - * Inner class to output the twenty four hour field. - */ - private static class TwentyFourHourField implements NumberRule { - private final NumberRule rule; - - /** - * Constructs an instance of {@link TwentyFourHourField} with the specified - * {@link NumberRule}. - * - * @param rule the rule - */ - TwentyFourHourField(final NumberRule rule) { - this.rule = rule; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return rule.estimateLength(); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - int value = calendar.get(Calendar.HOUR_OF_DAY); - if (value == 0) { - value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; - } - rule.appendTo(buffer, value); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final int value) throws IOException { - rule.appendTo(buffer, value); - } - } - - /** - * Inner class to output the numeric day in week. - */ - private static class DayInWeekField implements NumberRule { - private final NumberRule rule; - - DayInWeekField(final NumberRule rule) { - this.rule = rule; - } - - @Override - public int estimateLength() { - return rule.estimateLength(); - } - - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - final int value = calendar.get(Calendar.DAY_OF_WEEK); - rule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1); - } - - @Override - public void appendTo(final Appendable buffer, final int value) throws IOException { - rule.appendTo(buffer, value); - } - } - - /** - * Inner class to output the numeric day in week. - */ - private static class WeekYear implements NumberRule { - private final NumberRule rule; - - WeekYear(final NumberRule rule) { - this.rule = rule; - } - - @Override - public int estimateLength() { - return rule.estimateLength(); - } - - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - rule.appendTo(buffer, calendar.getWeekYear()); - } - - @Override - public void appendTo(final Appendable buffer, final int value) throws IOException { - rule.appendTo(buffer, value); - } - } - - - private static final ConcurrentMap cTimeZoneDisplayCache = - new ConcurrentHashMap<>(7); - - /** - * 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(final TimeZone tz, final boolean daylight, final int style, final Locale locale) { - final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); - // This is a very slow call, so cache the results. - return cTimeZoneDisplayCache.computeIfAbsent(key, k -> tz.getDisplayName(daylight, style, locale)); - } - - /** - * Inner class to output a time zone name. - */ - private static class TimeZoneNameRule implements Rule { - private final Locale locale; - private final int style; - private final String standard; - private final String daylight; - - /** - * Constructs an instance of {@link TimeZoneNameRule} with the specified properties. - * - * @param timeZone the time zone - * @param locale the locale - * @param style the style - */ - TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { - this.locale = LocaleUtils.toLocale(locale); - this.style = style; - this.standard = getTimeZoneDisplay(timeZone, false, style, locale); - this.daylight = getTimeZoneDisplay(timeZone, true, style, locale); - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - // We have no access to the Calendar object that will be passed to - // appendTo so base estimate on the TimeZone passed to the - // constructor - return Math.max(standard.length(), daylight.length()); - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - final TimeZone zone = calendar.getTimeZone(); - if (calendar.get(Calendar.DST_OFFSET) == 0) { - buffer.append(getTimeZoneDisplay(zone, false, style, locale)); - } else { - buffer.append(getTimeZoneDisplay(zone, true, style, locale)); - } - } - } - - /** - * 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); - - private final boolean colon; - - /** - * Constructs an instance of {@link TimeZoneNumberRule} with the specified properties. - * - * @param colon add colon between HH and MM in the output if {@code true} - */ - TimeZoneNumberRule(final boolean colon) { - this.colon = colon; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return 5; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - - int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); - - if (offset < 0) { - buffer.append('-'); - offset = -offset; - } else { - buffer.append('+'); - } - - final int hours = offset / (60 * 60 * 1000); - appendDigits(buffer, hours); - - if (colon) { - buffer.append(':'); - } - - final int minutes = offset / (60 * 1000) - 60 * hours; - appendDigits(buffer, minutes); - } - } - - /** - * Inner class to output a time zone as a number {@code +/-HHMM} - * or {@code +/-HH:MM}. - */ - private static class Iso8601_Rule implements Rule { - - // Sign TwoDigitHours or Z - static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); - // Sign TwoDigitHours Minutes or Z - static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); - // Sign TwoDigitHours : Minutes or Z - static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6); - - /** - * Factory method for Iso8601_Rules. - * - * @param tokenLen a token indicating the length of the TimeZone String to be formatted. - * @return an Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such - * rule exists, an IllegalArgumentException will be thrown. - */ - static Iso8601_Rule getRule(final int tokenLen) { - switch (tokenLen) { - case 1: - return ISO8601_HOURS; - case 2: - return ISO8601_HOURS_MINUTES; - case 3: - return ISO8601_HOURS_COLON_MINUTES; - default: - throw new IllegalArgumentException("invalid number of X"); - } - } - - private final int length; - - /** - * Constructs an instance of {@code Iso8601_Rule} with the specified properties. - * - * @param length The number of characters in output (unless Z is output) - */ - Iso8601_Rule(final int length) { - this.length = length; - } - - /** - * {@inheritDoc} - */ - @Override - public int estimateLength() { - return length; - } - - /** - * {@inheritDoc} - */ - @Override - public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); - if (offset == 0) { - buffer.append("Z"); - return; - } - - if (offset < 0) { - buffer.append('-'); - offset = -offset; - } else { - buffer.append('+'); - } - - final int hours = offset / (60 * 60 * 1000); - appendDigits(buffer, hours); - - if (length<5) { - return; - } - - if (length==6) { - buffer.append(':'); - } - - final int minutes = offset / (60 * 1000) - 60 * hours; - appendDigits(buffer, minutes); - } - } - - /** - * Inner class that acts as a compound key for time zone names. - */ - private static class TimeZoneDisplayKey { - private final TimeZone timeZone; - private final int style; - private final Locale locale; - - /** - * Constructs an instance of {@link 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 time zone style - * @param locale the time zone locale - */ - TimeZoneDisplayKey(final TimeZone timeZone, - final boolean daylight, final int style, final Locale locale) { - this.timeZone = timeZone; - if (daylight) { - this.style = style | 0x80000000; - } else { - this.style = style; - } - this.locale = LocaleUtils.toLocale(locale); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return (style * 31 + locale.hashCode() ) * 31 + timeZone.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof TimeZoneDisplayKey) { - final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj; - return - timeZone.equals(other.timeZone) && - style == other.style && - locale.equals(other.locale); - } - return false; - } - } } diff --git a/src/main/java/org/apache/commons/lang3/time/TimeZones.java b/src/main/java/org/apache/commons/lang3/time/TimeZones.java index c9fa4b24f..ff43a898e 100644 --- a/src/main/java/org/apache/commons/lang3/time/TimeZones.java +++ b/src/main/java/org/apache/commons/lang3/time/TimeZones.java @@ -28,10 +28,6 @@ import org.apache.commons.lang3.ObjectUtils; */ public class TimeZones { - /** Do not instantiate. */ - private TimeZones() { - } - /** * A public version of {@link java.util.TimeZone}'s package private {@code GMT_ID} field. */ @@ -55,4 +51,8 @@ public class TimeZones { return ObjectUtils.getIfNull(timeZone, TimeZone::getDefault); } + /** Do not instantiate. */ + private TimeZones() { + } + }