aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons')
-rw-r--r--src/main/java/org/apache/commons/lang3/AnnotationUtils.java355
-rw-r--r--src/main/java/org/apache/commons/lang3/ArchUtils.java138
-rw-r--r--src/main/java/org/apache/commons/lang3/ArraySorter.java141
-rw-r--r--src/main/java/org/apache/commons/lang3/ArrayUtils.java9646
-rw-r--r--src/main/java/org/apache/commons/lang3/BitField.java322
-rw-r--r--src/main/java/org/apache/commons/lang3/BooleanUtils.java1219
-rw-r--r--src/main/java/org/apache/commons/lang3/CharEncoding.java110
-rw-r--r--src/main/java/org/apache/commons/lang3/CharRange.java365
-rw-r--r--src/main/java/org/apache/commons/lang3/CharSequenceUtils.java389
-rw-r--r--src/main/java/org/apache/commons/lang3/CharSet.java283
-rw-r--r--src/main/java/org/apache/commons/lang3/CharSetUtils.java243
-rw-r--r--src/main/java/org/apache/commons/lang3/CharUtils.java521
-rw-r--r--src/main/java/org/apache/commons/lang3/Charsets.java69
-rw-r--r--src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/ClassPathUtils.java154
-rw-r--r--src/main/java/org/apache/commons/lang3/ClassUtils.java1623
-rw-r--r--src/main/java/org/apache/commons/lang3/Conversion.java1492
-rw-r--r--src/main/java/org/apache/commons/lang3/DoubleRange.java84
-rw-r--r--src/main/java/org/apache/commons/lang3/EnumUtils.java428
-rw-r--r--src/main/java/org/apache/commons/lang3/Functions.java662
-rw-r--r--src/main/java/org/apache/commons/lang3/IntegerRange.java84
-rw-r--r--src/main/java/org/apache/commons/lang3/JavaVersion.java326
-rw-r--r--src/main/java/org/apache/commons/lang3/LocaleUtils.java346
-rw-r--r--src/main/java/org/apache/commons/lang3/LongRange.java84
-rw-r--r--src/main/java/org/apache/commons/lang3/NotImplementedException.java137
-rw-r--r--src/main/java/org/apache/commons/lang3/NumberRange.java48
-rw-r--r--src/main/java/org/apache/commons/lang3/ObjectUtils.java1388
-rw-r--r--src/main/java/org/apache/commons/lang3/RandomStringUtils.java475
-rw-r--r--src/main/java/org/apache/commons/lang3/RandomUtils.java240
-rw-r--r--src/main/java/org/apache/commons/lang3/Range.java564
-rw-r--r--src/main/java/org/apache/commons/lang3/RegExUtils.java458
-rw-r--r--src/main/java/org/apache/commons/lang3/SerializationException.java76
-rw-r--r--src/main/java/org/apache/commons/lang3/SerializationUtils.java281
-rw-r--r--src/main/java/org/apache/commons/lang3/Streams.java551
-rw-r--r--src/main/java/org/apache/commons/lang3/StringEscapeUtils.java797
-rw-r--r--src/main/java/org/apache/commons/lang3/StringUtils.java9549
-rw-r--r--src/main/java/org/apache/commons/lang3/SystemProperties.java791
-rw-r--r--src/main/java/org/apache/commons/lang3/SystemUtils.java2094
-rw-r--r--src/main/java/org/apache/commons/lang3/ThreadUtils.java582
-rw-r--r--src/main/java/org/apache/commons/lang3/Validate.java1255
-rw-r--r--src/main/java/org/apache/commons/lang3/arch/Processor.java235
-rw-r--r--src/main/java/org/apache/commons/lang3/arch/package-info.java21
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/Builder.java86
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java1026
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/Diff.java107
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java932
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/DiffResult.java202
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/Diffable.java54
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java1124
-rwxr-xr-xsrc/main/java/org/apache/commons/lang3/builder/EqualsExclude.java36
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java936
-rwxr-xr-xsrc/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java36
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/IDKey.java72
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java217
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java98
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java134
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java877
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java520
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java1034
-rwxr-xr-xsrc/main/java/org/apache/commons/lang3/builder/ToStringExclude.java35
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java2555
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java40
-rw-r--r--src/main/java/org/apache/commons/lang3/builder/package-info.java33
-rw-r--r--src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java244
-rw-r--r--src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java69
-rw-r--r--src/main/java/org/apache/commons/lang3/compare/package-info.java23
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java175
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/AbstractFutureProxy.java78
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java105
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java95
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java333
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java373
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java122
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java93
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java65
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/Computable.java45
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java66
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java52
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java69
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java384
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java128
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java565
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java45
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java124
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java154
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java337
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java125
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java464
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/UncheckedExecutionException.java42
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/UncheckedFuture.java103
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java64
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutException.java42
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java373
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java23
-rw-r--r--src/main/java/org/apache/commons/lang3/concurrent/package-info.java531
-rw-r--r--src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java334
-rw-r--r--src/main/java/org/apache/commons/lang3/event/EventUtils.java129
-rw-r--r--src/main/java/org/apache/commons/lang3/event/package-info.java22
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java56
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/ContextedException.java253
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java254
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java149
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java103
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java981
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/UncheckedException.java41
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessException.java39
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/UncheckedInterruptedException.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationException.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/package-info.java26
-rw-r--r--src/main/java/org/apache/commons/lang3/function/BooleanConsumer.java68
-rw-r--r--src/main/java/org/apache/commons/lang3/function/Consumers.java49
-rw-r--r--src/main/java/org/apache/commons/lang3/function/Failable.java578
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java73
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java75
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java108
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableCallable.java37
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableConsumer.java75
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java40
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java68
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java101
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java93
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableFunction.java96
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java40
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java69
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java101
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java89
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java40
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java68
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java101
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java53
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java89
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailablePredicate.java104
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableRunnable.java35
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java38
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableSupplier.java39
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java58
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java58
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java58
-rw-r--r--src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/function/IntToCharFunction.java44
-rw-r--r--src/main/java/org/apache/commons/lang3/function/MethodInvokers.java259
-rw-r--r--src/main/java/org/apache/commons/lang3/function/Suppliers.java40
-rw-r--r--src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java43
-rw-r--r--src/main/java/org/apache/commons/lang3/function/TriConsumer.java71
-rw-r--r--src/main/java/org/apache/commons/lang3/function/TriFunction.java66
-rw-r--r--src/main/java/org/apache/commons/lang3/function/package-info.java29
-rw-r--r--src/main/java/org/apache/commons/lang3/math/Fraction.java908
-rw-r--r--src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java252
-rw-r--r--src/main/java/org/apache/commons/lang3/math/NumberUtils.java1850
-rw-r--r--src/main/java/org/apache/commons/lang3/math/package-info.java33
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/Mutable.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java205
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableByte.java381
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java407
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java408
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableInt.java371
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableLong.java371
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableObject.java121
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/MutableShort.java381
-rw-r--r--src/main/java/org/apache/commons/lang3/mutable/package-info.java24
-rw-r--r--src/main/java/org/apache/commons/lang3/package-info.java123
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java295
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java837
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java66
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java338
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java1005
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java124
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java1937
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/Typed.java37
-rw-r--r--src/main/java/org/apache/commons/lang3/reflect/package-info.java23
-rw-r--r--src/main/java/org/apache/commons/lang3/stream/IntStreams.java51
-rw-r--r--src/main/java/org/apache/commons/lang3/stream/LangCollectors.java167
-rw-r--r--src/main/java/org/apache/commons/lang3/stream/Streams.java816
-rw-r--r--src/main/java/org/apache/commons/lang3/stream/package-info.java26
-rw-r--r--src/main/java/org/apache/commons/lang3/text/CompositeFormat.java118
-rw-r--r--src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java528
-rw-r--r--src/main/java/org/apache/commons/lang3/text/FormatFactory.java45
-rw-r--r--src/main/java/org/apache/commons/lang3/text/FormattableUtils.java152
-rw-r--r--src/main/java/org/apache/commons/lang3/text/StrBuilder.java3065
-rw-r--r--src/main/java/org/apache/commons/lang3/text/StrLookup.java187
-rw-r--r--src/main/java/org/apache/commons/lang3/text/StrMatcher.java440
-rw-r--r--src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java1245
-rw-r--r--src/main/java/org/apache/commons/lang3/text/StrTokenizer.java1109
-rw-r--r--src/main/java/org/apache/commons/lang3/text/WordUtils.java717
-rw-r--r--src/main/java/org/apache/commons/lang3/text/package-info.java30
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java63
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java139
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/CodePointTranslator.java55
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java458
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/JavaUnicodeEscaper.java107
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java95
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java120
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java159
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java83
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java140
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnescaper.java68
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemover.java42
-rw-r--r--src/main/java/org/apache/commons/lang3/text/translate/package-info.java27
-rw-r--r--src/main/java/org/apache/commons/lang3/time/CalendarUtils.java140
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java415
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DateParser.java124
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DatePrinter.java178
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DateUtils.java1777
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java685
-rw-r--r--src/main/java/org/apache/commons/lang3/time/DurationUtils.java222
-rw-r--r--src/main/java/org/apache/commons/lang3/time/FastDateFormat.java671
-rw-r--r--src/main/java/org/apache/commons/lang3/time/FastDateParser.java1074
-rw-r--r--src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java1573
-rw-r--r--src/main/java/org/apache/commons/lang3/time/FastTimeZone.java95
-rw-r--r--src/main/java/org/apache/commons/lang3/time/FormatCache.java243
-rw-r--r--src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java113
-rw-r--r--src/main/java/org/apache/commons/lang3/time/StopWatch.java596
-rw-r--r--src/main/java/org/apache/commons/lang3/time/TimeZones.java58
-rw-r--r--src/main/java/org/apache/commons/lang3/time/package-info.java28
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/ImmutablePair.java214
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/ImmutableTriple.java170
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/MutablePair.java190
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/MutableTriple.java178
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/Pair.java233
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/Triple.java200
-rw-r--r--src/main/java/org/apache/commons/lang3/tuple/package-info.java22
-rw-r--r--src/main/java/org/apache/commons/lang3/util/FluentBitSet.java609
-rw-r--r--src/main/java/org/apache/commons/lang3/util/package-info.java22
240 files changed, 91406 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java
new file mode 100644
index 000000000..9d0571488
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java
@@ -0,0 +1,355 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.lang3.exception.UncheckedException;
+
+/**
+ * Helper methods for working with {@link Annotation} instances.
+ *
+ * <p>This class contains various utility methods that make working with
+ * annotations simpler.</p>
+ *
+ * <p>{@link Annotation} instances are always proxy objects; unfortunately
+ * dynamic proxies cannot be depended upon to know how to implement certain
+ * methods in the same manner as would be done by "natural" {@link Annotation}s.
+ * The methods presented in this class can be used to avoid that possibility. It
+ * is of course also possible for dynamic proxies to actually delegate their
+ * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
+ * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ *
+ * @since 3.0
+ */
+public class AnnotationUtils {
+
+ /**
+ * A style that prints annotations as recommended.
+ */
+ private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
+ /** Serialization version */
+ private static final long serialVersionUID = 1L;
+
+ {
+ setDefaultFullDetail(true);
+ setArrayContentDetail(true);
+ setUseClassName(true);
+ setUseShortClassName(true);
+ setUseIdentityHashCode(false);
+ setContentStart("(");
+ setContentEnd(")");
+ setFieldSeparator(", ");
+ setArrayStart("[");
+ setArrayEnd("]");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getShortClassName(final Class<?> cls) {
+ // formatter:off
+ return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
+ .map(iface -> "@" + iface.getName())
+ .orElse(StringUtils.EMPTY);
+ // 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.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public AnnotationUtils() {
+ }
+
+ /**
+ * Checks if two annotations are equal using the criteria for equality
+ * presented in the {@link Annotation#equals(Object)} API docs.
+ *
+ * @param a1 the first Annotation to compare, {@code null} returns
+ * {@code false} unless both are {@code null}
+ * @param a2 the second Annotation to compare, {@code null} returns
+ * {@code false} unless both are {@code null}
+ * @return {@code true} if the two annotations are {@code equal} or both
+ * {@code null}
+ */
+ public static boolean equals(final Annotation a1, final Annotation a2) {
+ if (a1 == a2) {
+ return true;
+ }
+ if (a1 == null || a2 == null) {
+ return false;
+ }
+ final Class<? extends Annotation> type1 = a1.annotationType();
+ final Class<? extends Annotation> type2 = a2.annotationType();
+ Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
+ Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
+ if (!type1.equals(type2)) {
+ return false;
+ }
+ try {
+ for (final Method m : type1.getDeclaredMethods()) {
+ if (m.getParameterTypes().length == 0
+ && isValidAnnotationMemberType(m.getReturnType())) {
+ final Object v1 = m.invoke(a1);
+ final Object v2 = m.invoke(a2);
+ if (!memberEquals(m.getReturnType(), v1, v2)) {
+ return false;
+ }
+ }
+ }
+ } catch (final ReflectiveOperationException ex) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Generate a hash code for the given annotation using the algorithm
+ * presented in the {@link Annotation#hashCode()} API docs.
+ *
+ * @param a the Annotation for a hash code calculation is desired, not
+ * {@code null}
+ * @return the calculated hash code
+ * @throws RuntimeException if an {@link Exception} is encountered during
+ * annotation member access
+ * @throws IllegalStateException if an annotation method invocation returns
+ * {@code null}
+ */
+ public static int hashCode(final Annotation a) {
+ int result = 0;
+ final Class<? extends Annotation> type = a.annotationType();
+ for (final Method m : type.getDeclaredMethods()) {
+ try {
+ final Object value = m.invoke(a);
+ if (value == null) {
+ throw new IllegalStateException(String.format("Annotation method %s returned null", m));
+ }
+ result += hashMember(m.getName(), value);
+ } catch (final ReflectiveOperationException ex) {
+ throw new UncheckedException(ex);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Generate a string representation of an Annotation, as suggested by
+ * {@link Annotation#toString()}.
+ *
+ * @param a the annotation of which a string representation is desired
+ * @return the standard string representation of an annotation, not
+ * {@code null}
+ */
+ 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 builder.build();
+ }
+
+ /**
+ * Checks if the specified type is permitted as an annotation member.
+ *
+ * <p>The Java language specification only permits certain types to be used
+ * in annotations. These include {@link String}, {@link Class}, primitive
+ * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
+ * these types.</p>
+ *
+ * @param type the type to check, {@code null}
+ * @return {@code true} if the type is a valid type to use in an annotation
+ */
+ public static boolean isValidAnnotationMemberType(Class<?> type) {
+ if (type == null) {
+ return false;
+ }
+ if (type.isArray()) {
+ type = type.getComponentType();
+ }
+ return type.isPrimitive() || type.isEnum() || type.isAnnotation()
+ || 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
+ * instances.
+ *
+ * @param type the type of the objects to be compared
+ * @param o1 the first object
+ * @param o2 the second object
+ * @return a flag whether these objects are equal
+ */
+ private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+ if (type.isArray()) {
+ return arrayMemberEquals(type.getComponentType(), o1, o2);
+ }
+ if (type.isAnnotation()) {
+ return equals((Annotation) o1, (Annotation) o2);
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * 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 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;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 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);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/ArchUtils.java b/src/main/java/org/apache/commons/lang3/ArchUtils.java
new file mode 100644
index 000000000..0a67d2c49
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ArchUtils.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.arch.Processor;
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * A utility class for the {@code os.arch} System Property. The class defines methods for
+ * identifying the architecture of the current JVM.
+ * <p>
+ * Important: The {@code os.arch} System Property returns the architecture used by the JVM
+ * not of the operating system.
+ * </p>
+ * @since 3.6
+ */
+public class ArchUtils {
+
+ private static final Map<String, Processor> ARCH_TO_PROCESSOR;
+
+ static {
+ ARCH_TO_PROCESSOR = new HashMap<>();
+ 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() {
+ final Processor processor = new Processor(Processor.Arch.BIT_64, Processor.Type.AARCH_64);
+ addProcessors(processor, "aarch64");
+ }
+
+ private static void init_X86_32Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_32, Processor.Type.X86);
+ addProcessors(processor, "x86", "i386", "i486", "i586", "i686", "pentium");
+ }
+
+ private static void init_X86_64Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_64, Processor.Type.X86);
+ addProcessors(processor, "x86_64", "amd64", "em64t", "universal");
+ }
+
+ private static void init_IA64_32Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_32, Processor.Type.IA_64);
+ addProcessors(processor, "ia64_32", "ia64n");
+ }
+
+ private static void init_IA64_64Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_64, Processor.Type.IA_64);
+ addProcessors(processor, "ia64", "ia64w");
+ }
+
+ private static void init_PPC_32Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_32, Processor.Type.PPC);
+ addProcessors(processor, "ppc", "power", "powerpc", "power_pc", "power_rs");
+ }
+
+ private static void init_PPC_64Bit() {
+ final Processor processor = new Processor(Processor.Arch.BIT_64, Processor.Type.PPC);
+ addProcessors(processor, "ppc64", "power64", "powerpc64", "power_pc64", "power_rs64");
+ }
+
+ /**
+ * Adds the given {@link Processor} with the given key {@link String} to the map.
+ *
+ * @param key The key as {@link String}.
+ * @param processor The {@link Processor} to add.
+ * @throws IllegalStateException If the key already exists.
+ */
+ private static void addProcessor(final String key, final Processor processor) {
+ if (ARCH_TO_PROCESSOR.containsKey(key)) {
+ throw new IllegalStateException("Key " + key + " already exists in processor map");
+ }
+ ARCH_TO_PROCESSOR.put(key, processor);
+ }
+
+ /**
+ * Adds the given {@link Processor} with the given keys to the map.
+ *
+ * @param keys The keys.
+ * @param processor The {@link Processor} to add.
+ * @throws IllegalStateException If the key already exists.
+ */
+ private static void addProcessors(final Processor processor, final String... keys) {
+ Streams.of(keys).forEach(e -> addProcessor(e, processor));
+ }
+
+ /**
+ * Gets a {@link Processor} object of the current JVM.
+ *
+ * <p>
+ * Important: The os.arch System Property returns the architecture used by the JVM
+ * not of the operating system.
+ * </p>
+ *
+ * @return A {@link Processor} when supported, else {@code null}.
+ */
+ public static Processor getProcessor() {
+ return getProcessor(SystemProperties.getOsArch());
+ }
+
+ /**
+ * Gets a {@link Processor} object the given value {@link String}. The {@link String} must be
+ * like a value returned by the {@code os.arch} System Property.
+ *
+ * @param value A {@link String} like a value returned by the {@code os.arch} System Property.
+ * @return A {@link Processor} when it exists, else {@code null}.
+ */
+ public static Processor getProcessor(final String value) {
+ return ARCH_TO_PROCESSOR.get(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ArraySorter.java b/src/main/java/org/apache/commons/lang3/ArraySorter.java
new file mode 100644
index 000000000..2208bd902
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ArraySorter.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Sorts and returns arrays in the fluent style.
+ *
+ * @since 3.12.0
+ */
+public class ArraySorter {
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(byte[])
+ */
+ public static byte[] sort(final byte[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(char[])
+ */
+ public static char[] sort(final char[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(double[])
+ */
+ public static double[] sort(final double[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(float[])
+ */
+ public static float[] sort(final float[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(int[])
+ */
+ public static int[] sort(final int[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(long[])
+ */
+ public static long[] sort(final long[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(short[])
+ */
+ public static short[] sort(final short[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param <T> the array type.
+ * @param array the array to sort.
+ * @return the given array.
+ * @see Arrays#sort(Object[])
+ */
+ public static <T> T[] sort(final T[] array) {
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Sorts and returns the given array.
+ *
+ * @param <T> the array type.
+ * @param array the array to sort.
+ * @param comparator the comparator to determine the order of the array. A {@code null} value uses the elements'
+ * {@link Comparable natural ordering}.
+ * @return the given array.
+ * @see Arrays#sort(Object[])
+ */
+ public static <T> T[] sort(final T[] array, final Comparator<? super T> comparator) {
+ Arrays.sort(array, comparator);
+ return array;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ArrayUtils.java b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
new file mode 100644
index 000000000..95bf83964
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
@@ -0,0 +1,9646 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * Operations on arrays, primitive arrays (like {@code int[]}) and
+ * primitive wrapper arrays (like {@code Integer[]}).
+ * <p>
+ * This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null}
+ * array input. However, an Object array that contains a {@code null}
+ * element may throw an exception. Each method documents its behavior.
+ * </p>
+ * <p>
+ * #ThreadSafe#
+ * </p>
+ * @since 2.0
+ */
+public class ArrayUtils {
+
+ /**
+ * An empty immutable {@code boolean} array.
+ */
+ public static final boolean[] EMPTY_BOOLEAN_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Boolean} array.
+ */
+ public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@code byte} array.
+ */
+ public static final byte[] EMPTY_BYTE_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Byte} array.
+ */
+ public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@code char} array.
+ */
+ public static final char[] EMPTY_CHAR_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Character} array.
+ */
+ public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Class} array.
+ */
+ public static final Class<?>[] EMPTY_CLASS_ARRAY = {};
+
+ /**
+ * An empty immutable {@code double} array.
+ */
+ public static final double[] EMPTY_DOUBLE_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Double} array.
+ */
+ public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Field} array.
+ *
+ * @since 3.10
+ */
+ public static final Field[] EMPTY_FIELD_ARRAY = {};
+
+ /**
+ * An empty immutable {@code float} array.
+ */
+ public static final float[] EMPTY_FLOAT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Float} array.
+ */
+ public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@code int} array.
+ */
+ public static final int[] EMPTY_INT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Integer} array.
+ */
+ public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@code long} array.
+ */
+ public static final long[] EMPTY_LONG_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Long} array.
+ */
+ public static final Long[] EMPTY_LONG_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Method} array.
+ *
+ * @since 3.10
+ */
+ public static final Method[] EMPTY_METHOD_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Object} array.
+ */
+ public static final Object[] EMPTY_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@code short} array.
+ */
+ public static final short[] EMPTY_SHORT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Short} array.
+ */
+ public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = {};
+
+ /**
+ * An empty immutable {@link String} array.
+ */
+ public static final String[] EMPTY_STRING_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Throwable} array.
+ *
+ * @since 3.10
+ */
+ public static final Throwable[] EMPTY_THROWABLE_ARRAY = {};
+
+ /**
+ * An empty immutable {@link Type} array.
+ *
+ * @since 3.10
+ */
+ public static final Type[] EMPTY_TYPE_ARRAY = {};
+
+ /**
+ * The index value when an element is not found in a list or array: {@code -1}.
+ * This value is returned by methods in this class and can also be used in comparisons with values returned by
+ * various method from {@link java.util.List}.
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, true) = [true]
+ * ArrayUtils.add([true], false) = [true, false]
+ * ArrayUtils.add([true, false], true) = [true, false, true]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static boolean[] add(final boolean[] array, final boolean element) {
+ final boolean[] newArray = (boolean[]) copyArrayGrow1(array, Boolean.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0, true) = [true]
+ * ArrayUtils.add([true], 0, false) = [false, true]
+ * ArrayUtils.add([false], 1, true) = [false, true]
+ * ArrayUtils.add([true, false], 1, true) = [true, true, false]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, boolean[], boolean...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static boolean[] add(final boolean[] array, final int index, final boolean element) {
+ return (boolean[]) add(array, index, Boolean.valueOf(element), Boolean.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static byte[] add(final byte[] array, final byte element) {
+ final byte[] newArray = (byte[]) copyArrayGrow1(array, Byte.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 3) = [2, 6, 3]
+ * ArrayUtils.add([2, 6], 0, 1) = [1, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, byte[], byte...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static byte[] add(final byte[] array, final int index, final byte element) {
+ return (byte[]) add(array, index, Byte.valueOf(element), Byte.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, '0') = ['0']
+ * ArrayUtils.add(['1'], '0') = ['1', '0']
+ * ArrayUtils.add(['1', '0'], '1') = ['1', '0', '1']
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static char[] add(final char[] array, final char element) {
+ final char[] newArray = (char[]) copyArrayGrow1(array, Character.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0, 'a') = ['a']
+ * ArrayUtils.add(['a'], 0, 'b') = ['b', 'a']
+ * ArrayUtils.add(['a', 'b'], 0, 'c') = ['c', 'a', 'b']
+ * ArrayUtils.add(['a', 'b'], 1, 'k') = ['a', 'k', 'b']
+ * ArrayUtils.add(['a', 'b', 'c'], 1, 't') = ['a', 't', 'b', 'c']
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, char[], char...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static char[] add(final char[] array, final int index, final char element) {
+ return (char[]) add(array, index, Character.valueOf(element), Character.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ *
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static double[] add(final double[] array, final double element) {
+ final double[] newArray = (double[]) copyArrayGrow1(array, Double.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1.1], 0, 2.2) = [2.2, 1.1]
+ * ArrayUtils.add([2.3, 6.4], 2, 10.5) = [2.3, 6.4, 10.5]
+ * ArrayUtils.add([2.6, 6.7], 0, -4.8) = [-4.8, 2.6, 6.7]
+ * ArrayUtils.add([2.9, 6.0, 0.3], 2, 1.0) = [2.9, 6.0, 1.0, 0.3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, double[], double...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static double[] add(final double[] array, final int index, final double element) {
+ return (double[]) add(array, index, Double.valueOf(element), Double.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static float[] add(final float[] array, final float element) {
+ final float[] newArray = (float[]) copyArrayGrow1(array, Float.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1.1f], 0, 2.2f) = [2.2f, 1.1f]
+ * ArrayUtils.add([2.3f, 6.4f], 2, 10.5f) = [2.3f, 6.4f, 10.5f]
+ * ArrayUtils.add([2.6f, 6.7f], 0, -4.8f) = [-4.8f, 2.6f, 6.7f]
+ * ArrayUtils.add([2.9f, 6.0f, 0.3f], 2, 1.0f) = [2.9f, 6.0f, 1.0f, 0.3f]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, float[], float...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static float[] add(final float[] array, final int index, final float element) {
+ return (float[]) add(array, index, Float.valueOf(element), Float.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static int[] add(final int[] array, final int element) {
+ final int[] newArray = (int[]) copyArrayGrow1(array, Integer.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 10) = [2, 6, 10]
+ * ArrayUtils.add([2, 6], 0, -4) = [-4, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, int[], int...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static int[] add(final int[] array, final int index, final int element) {
+ return (int[]) add(array, index, Integer.valueOf(element), Integer.TYPE);
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1L], 0, 2L) = [2L, 1L]
+ * ArrayUtils.add([2L, 6L], 2, 10L) = [2L, 6L, 10L]
+ * ArrayUtils.add([2L, 6L], 0, -4L) = [-4L, 2L, 6L]
+ * ArrayUtils.add([2L, 6L, 3L], 2, 1L) = [2L, 6L, 1L, 3L]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, long[], long...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static long[] add(final long[] array, final int index, final long element) {
+ return (long[]) add(array, index, Long.valueOf(element), Long.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static long[] add(final long[] array, final long element) {
+ final long[] newArray = (long[]) copyArrayGrow1(array, Long.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Underlying implementation of add(array, index, element) methods.
+ * The last parameter is the class, which may not equal element.getClass
+ * for primitives.
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @param clss the type of the element being added
+ * @return A new array containing the existing elements and the new element
+ */
+ private static Object add(final Object array, final int index, final Object element, final Class<?> clss) {
+ if (array == null) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: 0");
+ }
+ final Object joinedArray = Array.newInstance(clss, 1);
+ Array.set(joinedArray, 0, element);
+ return joinedArray;
+ }
+ final int length = Array.getLength(array);
+ if (index > length || index < 0) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+ final Object result = Array.newInstance(clss, length + 1);
+ System.arraycopy(array, 0, result, 0, index);
+ Array.set(result, index, element);
+ if (index < length) {
+ System.arraycopy(array, index, result, index + 1, length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add([1], 0, 2) = [2, 1]
+ * ArrayUtils.add([2, 6], 2, 10) = [2, 6, 10]
+ * ArrayUtils.add([2, 6], 0, -4) = [-4, 2, 6]
+ * ArrayUtils.add([2, 6, 3], 2, 1) = [2, 6, 1, 3]
+ * </pre>
+ *
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt; array.length).
+ * @deprecated this method has been superseded by {@link #insert(int, short[], short...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static short[] add(final short[] array, final int index, final short element) {
+ return (short[]) add(array, index, Short.valueOf(element), Short.TYPE);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0) = [0]
+ * ArrayUtils.add([1], 0) = [1, 0]
+ * ArrayUtils.add([1, 0], 1) = [1, 0, 1]
+ * </pre>
+ *
+ * @param array the array to copy and add the element to, may be {@code null}
+ * @param element the object to add at the last index of the new array
+ * @return A new array containing the existing elements plus the new element
+ * @since 2.1
+ */
+ public static short[] add(final short[] array, final short element) {
+ final short[] newArray = (short[]) copyArrayGrow1(array, Short.TYPE);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Inserts the specified element at the specified position in the array.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array plus the given element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element.
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, 0, null) = IllegalArgumentException
+ * ArrayUtils.add(null, 0, "a") = ["a"]
+ * ArrayUtils.add(["a"], 1, null) = ["a", null]
+ * ArrayUtils.add(["a"], 1, "b") = ["a", "b"]
+ * ArrayUtils.add(["a", "b"], 3, "c") = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to add the element to, may be {@code null}
+ * @param index the position of the new object
+ * @param element the object to add
+ * @return A new array containing the existing elements and the new element
+ * @throws IndexOutOfBoundsException if the index is out of range (index &lt; 0 || index &gt; array.length).
+ * @throws IllegalArgumentException if both array and element are null
+ * @deprecated this method has been superseded by {@link #insert(int, Object[], Object...) insert(int, T[], T...)} and
+ * may be removed in a future release. Please note the handling of {@code null} input arrays differs
+ * in the new method: inserting {@code X} into a {@code null} array results in {@code null} not {@code X}.
+ */
+ @Deprecated
+ public static <T> T[] add(final T[] array, final int index, final T element) {
+ final Class<T> clss;
+ if (array != null) {
+ clss = getComponentType(array);
+ } else if (element != null) {
+ clss = ObjectUtils.getClass(element);
+ } else {
+ throw new IllegalArgumentException("Array and element cannot both be null");
+ }
+ return (T[]) add(array, index, element, clss);
+ }
+
+
+ /**
+ * Copies the given array and adds the given element at the end of the new array.
+ * <p>
+ * The new array contains the same elements of the input
+ * array plus the given element in the last position. The component type of
+ * the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned
+ * whose component type is the same as the element, unless the element itself is null,
+ * in which case the return type is Object[]
+ * </p>
+ * <pre>
+ * ArrayUtils.add(null, null) = IllegalArgumentException
+ * ArrayUtils.add(null, "a") = ["a"]
+ * ArrayUtils.add(["a"], null) = ["a", null]
+ * ArrayUtils.add(["a"], "b") = ["a", "b"]
+ * ArrayUtils.add(["a", "b"], "c") = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to "add" the element to, may be {@code null}
+ * @param element the object to add, may be {@code null}
+ * @return A new array containing the existing elements plus the new element
+ * The returned array type will be that of the input array (unless null),
+ * in which case it will have the same type as the element.
+ * If both are null, an IllegalArgumentException is thrown
+ * @since 2.1
+ * @throws IllegalArgumentException if both arguments are null
+ */
+ public static <T> T[] add(final T[] array, final T element) {
+ final Class<?> type;
+ if (array != null) {
+ type = array.getClass().getComponentType();
+ } else if (element != null) {
+ type = element.getClass();
+ } else {
+ throw new IllegalArgumentException("Arguments cannot both be null");
+ }
+ @SuppressWarnings("unchecked") // type must be T
+ final
+ T[] newArray = (T[]) copyArrayGrow1(array, type);
+ newArray[newArray.length - 1] = element;
+ return newArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new boolean[] array.
+ * @since 2.1
+ */
+ public static boolean[] addAll(final boolean[] array1, final boolean... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final boolean[] joinedArray = new boolean[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new byte[] array.
+ * @since 2.1
+ */
+ public static byte[] addAll(final byte[] array1, final byte... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final byte[] joinedArray = new byte[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new char[] array.
+ * @since 2.1
+ */
+ public static char[] addAll(final char[] array1, final char... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final char[] joinedArray = new char[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new double[] array.
+ * @since 2.1
+ */
+ public static double[] addAll(final double[] array1, final double... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final double[] joinedArray = new double[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new float[] array.
+ * @since 2.1
+ */
+ public static float[] addAll(final float[] array1, final float... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final float[] joinedArray = new float[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new int[] array.
+ * @since 2.1
+ */
+ public static int[] addAll(final int[] array1, final int... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final int[] joinedArray = new int[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new long[] array.
+ * @since 2.1
+ */
+ public static long[] addAll(final long[] array1, final long... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final long[] joinedArray = new long[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * </pre>
+ *
+ * @param array1 the first array whose elements are added to the new array.
+ * @param array2 the second array whose elements are added to the new array.
+ * @return The new short[] array.
+ * @since 2.1
+ */
+ public static short[] addAll(final short[] array1, final short... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final short[] joinedArray = new short[array1.length + array2.length];
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ return joinedArray;
+ }
+
+ /**
+ * Adds all the elements of the given arrays into a new array.
+ * <p>
+ * The new array contains all of the element of {@code array1} followed
+ * by all of the elements {@code array2}. When an array is returned, it is always
+ * a new array.
+ * </p>
+ * <pre>
+ * ArrayUtils.addAll(null, null) = null
+ * ArrayUtils.addAll(array1, null) = cloned copy of array1
+ * ArrayUtils.addAll(null, array2) = cloned copy of array2
+ * ArrayUtils.addAll([], []) = []
+ * ArrayUtils.addAll([null], [null]) = [null, null]
+ * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array1 the first array whose elements are added to the new array, may be {@code null}
+ * @param array2 the second array whose elements are added to the new array, may be {@code null}
+ * @return The new array, {@code null} if both arrays are {@code null}.
+ * The type of the new array is the type of the first array,
+ * unless the first array is null, in which case the type is the same as the second array.
+ * @since 2.1
+ * @throws IllegalArgumentException if the array types are incompatible
+ */
+ public static <T> T[] addAll(final T[] array1, @SuppressWarnings("unchecked") final T... array2) {
+ if (array1 == null) {
+ return clone(array2);
+ }
+ if (array2 == null) {
+ return clone(array1);
+ }
+ final Class<T> type1 = getComponentType(array1);
+ final T[] joinedArray = newInstance(type1, array1.length + array2.length);
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ try {
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ } catch (final ArrayStoreException ase) {
+ // Check if problem was due to incompatible types
+ /*
+ * We do this here, rather than before the copy because:
+ * - it would be a wasted check most of the time
+ * - safer, in case check turns out to be too strict
+ */
+ final Class<?> type2 = array2.getClass().getComponentType();
+ if (!type1.isAssignableFrom(type2)) {
+ throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of "
+ + type1.getName(), ase);
+ }
+ throw ase; // No, so rethrow original
+ }
+ return joinedArray;
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, true) = [true]
+ * ArrayUtils.addFirst([true], false) = [false, true]
+ * ArrayUtils.addFirst([true, false], true) = [true, true, false]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static boolean[] addFirst(final boolean[] array, final boolean element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static byte[] addFirst(final byte[] array, final byte element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, '1') = ['1']
+ * ArrayUtils.addFirst(['1'], '0') = ['0', '1']
+ * ArrayUtils.addFirst(['1', '0'], '1') = ['1', '1', '0']
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static char[] addFirst(final char[] array, final char element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static double[] addFirst(final double[] array, final double element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static float[] addFirst(final float[] array, final float element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static int[] addFirst(final int[] array, final int element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static long[] addFirst(final long[] array, final long element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element.
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, 1) = [1]
+ * ArrayUtils.addFirst([1], 0) = [0, 1]
+ * ArrayUtils.addFirst([1, 0], 1) = [1, 1, 0]
+ * </pre>
+ *
+ * @param array the array to "add" the element to, may be {@code null}.
+ * @param element the object to add.
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element.
+ * @since 3.10
+ */
+ public static short[] addFirst(final short[] array, final short element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Copies the given array and adds the given element at the beginning of the new array.
+ * <p>
+ * The new array contains the same elements of the input array plus the given element in the first position. The
+ * component type of the new array is the same as that of the input array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, a new one element array is returned whose component type is the same as the
+ * element, unless the element itself is null, in which case the return type is Object[]
+ * </p>
+ * <pre>
+ * ArrayUtils.addFirst(null, null) = IllegalArgumentException
+ * ArrayUtils.addFirst(null, "a") = ["a"]
+ * ArrayUtils.addFirst(["a"], null) = [null, "a"]
+ * ArrayUtils.addFirst(["a"], "b") = ["b", "a"]
+ * ArrayUtils.addFirst(["a", "b"], "c") = ["c", "a", "b"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to "add" the element to, may be {@code null}
+ * @param element the object to add, may be {@code null}
+ * @return A new array containing the existing elements plus the new element The returned array type will be that of
+ * the input array (unless null), in which case it will have the same type as the element. If both are null,
+ * an IllegalArgumentException is thrown
+ * @since 3.10
+ * @throws IllegalArgumentException if both arguments are null
+ */
+ public static <T> T[] addFirst(final T[] array, final T element) {
+ return array == null ? add(array, element) : insert(0, array, element);
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static boolean[] clone(final boolean[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static byte[] clone(final byte[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static char[] clone(final char[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static double[] clone(final double[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static float[] clone(final float[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static int[] clone(final int[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static long[] clone(final long[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static short[] clone(final short[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Shallow clones an array returning a typecast result and handling
+ * {@code null}.
+ * <p>
+ * The objects in the array are not cloned, thus there is no special
+ * handling for multi-dimensional arrays.
+ * </p>
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to shallow clone, may be {@code null}
+ * @return the cloned array, {@code null} if {@code null} input
+ */
+ public static <T> T[] clone(final T[] array) {
+ return array != null ? array.clone() : null;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final boolean[] array, final boolean valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final byte[] array, final byte valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ * @since 2.1
+ */
+ public static boolean contains(final char[] array, final char valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final double[] array, final double valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if a value falling within the given tolerance is in the
+ * given array. If the array contains a value within the inclusive range
+ * defined by (value - tolerance) to (value + tolerance).
+ * <p>
+ * The method returns {@code false} if a {@code null} array
+ * is passed in.
+ * </p>
+ *
+ * @param array the array to search
+ * @param valueToFind the value to find
+ * @param tolerance the array contains the tolerance of the search
+ * @return true if value falling within tolerance is in array
+ */
+ public static boolean contains(final double[] array, final double valueToFind, final double tolerance) {
+ return indexOf(array, valueToFind, 0, tolerance) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final float[] array, final float valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final int[] array, final int valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final long[] array, final long valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the object is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param objectToFind the object to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final Object[] array, final Object objectToFind) {
+ return indexOf(array, objectToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the value is in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param valueToFind the value to find
+ * @return {@code true} if the array contains the object
+ */
+ public static boolean contains(final short[] array, final short valueToFind) {
+ return indexOf(array, valueToFind) != INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if any of the objects are in the given array.
+ * <p>
+ * The method returns {@code false} if a {@code null} array is passed in.
+ * </p>
+ *
+ * @param array the array to search through
+ * @param objectsToFind any of the objects to find
+ * @return {@code true} if the array contains any of the objects
+ * @since 3.13.0
+ */
+ public static boolean containsAny(final Object[] array, final Object... objectsToFind) {
+ return Streams.of(objectsToFind).anyMatch(e -> contains(array, e));
+ }
+
+ /**
+ * Returns a copy of the given array of size 1 greater than the argument.
+ * The last value of the array is left to the default value.
+ *
+ * @param array The array to copy, must not be {@code null}.
+ * @param newArrayComponentType If {@code array} is {@code null}, create a
+ * size 1 array of this type.
+ * @return A new copy of the array of size 1 greater than the input.
+ */
+ private static Object copyArrayGrow1(final Object array, final Class<?> newArrayComponentType) {
+ if (array != null) {
+ final int arrayLength = Array.getLength(array);
+ final Object newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1);
+ System.arraycopy(array, 0, newArray, 0, arrayLength);
+ return newArray;
+ }
+ return Array.newInstance(newArrayComponentType, 1);
+ }
+
+ /**
+ * Gets the nTh element of an array or null if the index is out of bounds or the array is null.
+ *
+ * @param <T> The type of array elements.
+ * @param array The array to index.
+ * @param index The index
+ * @return the nTh element of an array or null if the index is out of bounds or the array is null.
+ * @since 3.11
+ */
+ public static <T> T get(final T[] array, final int index) {
+ return get(array, index, null);
+ }
+
+ /**
+ * Gets the nTh element of an array or a default value if the index is out of bounds.
+ *
+ * @param <T> The type of array elements.
+ * @param array The array to index.
+ * @param index The index
+ * @param defaultValue The return value of the given index is out of bounds.
+ * @return the nTh element of an array or a default value if the index is out of bounds.
+ * @since 3.11
+ */
+ public static <T> T get(final T[] array, final int index, final T defaultValue) {
+ return isArrayIndexValid(array, index) ? array[index] : defaultValue;
+ }
+
+ /**
+ * Gets an array's component type.
+ *
+ * @param <T> The array type.
+ * @param array The array.
+ * @return The component type.
+ * @since 3.13.0
+ */
+ public static <T> Class<T> getComponentType(final T[] array) {
+ return ClassUtils.getComponentType(ObjectUtils.getClass(array));
+ }
+
+ /**
+ * Returns the length of the specified array.
+ * This method can deal with {@link Object} arrays and with primitive arrays.
+ * <p>
+ * If the input array is {@code null}, {@code 0} is returned.
+ * </p>
+ * <pre>
+ * ArrayUtils.getLength(null) = 0
+ * ArrayUtils.getLength([]) = 0
+ * ArrayUtils.getLength([null]) = 1
+ * ArrayUtils.getLength([true, false]) = 2
+ * ArrayUtils.getLength([1, 2, 3]) = 3
+ * ArrayUtils.getLength(["a", "b", "c"]) = 3
+ * </pre>
+ *
+ * @param array the array to retrieve the length from, may be null
+ * @return The length of the array, or {@code 0} if the array is {@code null}
+ * @throws IllegalArgumentException if the object argument is not an array.
+ * @since 2.1
+ */
+ public static int getLength(final Object array) {
+ return array != null ? Array.getLength(array) : 0;
+ }
+
+ /**
+ * Get a hash code for an array handling multidimensional arrays correctly.
+ * <p>
+ * Multi-dimensional primitive arrays are also handled correctly by this method.
+ * </p>
+ *
+ * @param array the array to get a hash code for, {@code null} returns zero
+ * @return a hash code for the array
+ */
+ public static int hashCode(final Object array) {
+ return new HashCodeBuilder().append(array).toHashCode();
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ * <p>
+ * This method returns an empty BitSet for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final boolean[] array, final boolean valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ * <p>
+ * This method returns an empty BitSet for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null}
+ * array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final boolean[] array, final boolean valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final byte[] array, final byte valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final byte[] array, final byte valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final char[] array, final char valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final char[] array, final char valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final double[] array, final double valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value within a given tolerance in the array.
+ *
+ * <p>
+ * This method will return all the indices of the value which fall between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance, each time between the nearest integers.
+ * </p>
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param tolerance tolerance of the search
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final double[] array, final double valueToFind, final double tolerance) {
+ return indexesOf(array, valueToFind, 0, tolerance);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final double[] array, final double valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>
+ * This method will return the indices of the values which fall between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance, between the nearest integers.
+ * </p>
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @param tolerance tolerance of the search
+ * @return a BitSet of the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex, tolerance);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final float[] array, final float valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final float[] array, final float valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final int[] array, final int valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final int[] array, final int valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final long[] array, final long valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final long[] array, final long valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given object in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @return a BitSet of all the indices of the object within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final Object[] array, final Object objectToFind) {
+ return indexesOf(array, objectToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given object in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the object within the array starting at the index,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final Object[] array, final Object objectToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, objectToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the indices of the given value in the array.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final short[] array, final short valueToFind) {
+ return indexesOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the indices of the given value in the array starting at the given index.
+ *
+ * <p>This method returns an empty BitSet for a {@code null} input array.</p>
+ *
+ * <p>A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return an empty BitSet.</p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return a BitSet of all the indices of the value within the array,
+ * an empty BitSet if not found or {@code null} array input
+ * @since 3.10
+ */
+ public static BitSet indexesOf(final short[] array, final short valueToFind, int startIndex) {
+ final BitSet bitSet = new BitSet();
+
+ if (array == null) {
+ return bitSet;
+ }
+
+ while (startIndex < array.length) {
+ startIndex = indexOf(array, valueToFind, startIndex);
+
+ if (startIndex == INDEX_NOT_FOUND) {
+ break;
+ }
+
+ bitSet.set(startIndex);
+ ++startIndex;
+ }
+
+ return bitSet;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final boolean[] array, final boolean valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null}
+ * array input
+ */
+ public static int indexOf(final boolean[] array, final boolean valueToFind, int startIndex) {
+ if (isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final byte[] array, final byte valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final byte[] array, final byte valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int indexOf(final char[] array, final char valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int indexOf(final char[] array, final char valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final double[] array, final double valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value within a given tolerance in the array.
+ * This method will return the index of the first value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final double[] array, final double valueToFind, final double tolerance) {
+ return indexOf(array, valueToFind, 0, tolerance);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final double[] array, final double valueToFind, int startIndex) {
+ if (isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ final boolean searchNaN = Double.isNaN(valueToFind);
+ for (int i = startIndex; i < array.length; i++) {
+ final double element = array[i];
+ if (valueToFind == element || searchNaN && Double.isNaN(element)) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * This method will return the index of the first value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) {
+ if (isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ final double min = valueToFind - tolerance;
+ final double max = valueToFind + tolerance;
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] >= min && array[i] <= max) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final float[] array, final float valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final float[] array, final float valueToFind, int startIndex) {
+ if (isEmpty(array)) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ final boolean searchNaN = Float.isNaN(valueToFind);
+ for (int i = startIndex; i < array.length; i++) {
+ final float element = array[i];
+ if (valueToFind == element || searchNaN && Float.isNaN(element)) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final int[] array, final int valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final int[] array, final int valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array, {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null}
+ * array input
+ */
+ public static int indexOf(final long[] array, final long valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final long[] array, final long valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given object in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @return the index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final Object[] array, final Object objectToFind) {
+ return indexOf(array, objectToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given object in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @param startIndex the index to start searching at
+ * @return the index of the object within the array starting at the index,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final Object[] array, final Object objectToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ if (objectToFind == null) {
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = startIndex; i < array.length; i++) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the index of the given value in the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final short[] array, final short valueToFind) {
+ return indexOf(array, valueToFind, 0);
+ }
+
+ /**
+ * Finds the index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex is treated as zero. A startIndex larger than the array
+ * length will return {@link #INDEX_NOT_FOUND} ({@code -1}).
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the index to start searching at
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int indexOf(final short[] array, final short valueToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex; i < array.length; i++) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static boolean[] insert(final int index, final boolean[] array, final boolean... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final boolean[] result = new boolean[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static byte[] insert(final int index, final byte[] array, final byte... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final byte[] result = new byte[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static char[] insert(final int index, final char[] array, final char... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final char[] result = new char[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static double[] insert(final int index, final double[] array, final double... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final double[] result = new double[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static float[] insert(final int index, final float[] array, final float... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final float[] result = new float[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static int[] insert(final int index, final int[] array, final int... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final int[] result = new int[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static long[] insert(final int index, final long[] array, final long... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final long[] result = new long[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ public static short[] insert(final int index, final short[] array, final short... values) {
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final short[] result = new short[array.length + values.length];
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Inserts elements into an array at the given index (starting from zero).
+ *
+ * <p>When an array is returned, it is always a new array.</p>
+ *
+ * <pre>
+ * ArrayUtils.insert(index, null, null) = null
+ * ArrayUtils.insert(index, array, null) = cloned copy of 'array'
+ * ArrayUtils.insert(index, null, values) = null
+ * </pre>
+ *
+ * @param <T> The type of elements in {@code array} and {@code values}
+ * @param index the position within {@code array} to insert the new values
+ * @param array the array to insert the values into, may be {@code null}
+ * @param values the new values to insert, may be {@code null}
+ * @return The new array.
+ * @throws IndexOutOfBoundsException if {@code array} is provided
+ * and either {@code index < 0} or {@code index > array.length}
+ * @since 3.6
+ */
+ @SafeVarargs
+ public static <T> T[] insert(final int index, final T[] array, final T... values) {
+ /*
+ * Note on use of @SafeVarargs:
+ *
+ * By returning null when 'array' is null, we avoid returning the vararg
+ * array to the caller. We also avoid relying on the type of the vararg
+ * array, by inspecting the component type of 'array'.
+ */
+
+ if (array == null) {
+ return null;
+ }
+ if (ArrayUtils.isEmpty(values)) {
+ return clone(array);
+ }
+ if (index < 0 || index > array.length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
+ }
+
+ final Class<T> type = getComponentType(array);
+ final int length = array.length + values.length;
+ final T[] result = newInstance(type, length);
+
+ System.arraycopy(values, 0, result, index, values.length);
+ if (index > 0) {
+ System.arraycopy(array, 0, result, 0, index);
+ }
+ if (index < array.length) {
+ System.arraycopy(array, index, result, index + values.length, array.length - index);
+ }
+ return result;
+ }
+
+ /**
+ * Checks if an array is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ */
+ private static boolean isArrayEmpty(final Object array) {
+ return getLength(array) == 0;
+ }
+
+ /**
+ * Returns whether a given array can safely be accessed at the given index.
+ *
+ * <pre>
+ * ArrayUtils.isArrayIndexValid(null, 0) = false
+ * ArrayUtils.isArrayIndexValid([], 0) = false
+ * ArrayUtils.isArrayIndexValid(["a"], 0) = true
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to inspect, may be null
+ * @param index the index of the array to be inspected
+ * @return Whether the given index is safely-accessible in the given array
+ * @since 3.8
+ */
+ public static <T> boolean isArrayIndexValid(final T[] array, final int index) {
+ return index >= 0 && getLength(array) > index;
+ }
+
+ /**
+ * Checks if an array of primitive booleans is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final boolean[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive bytes is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final byte[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive chars is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final char[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive doubles is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final double[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive floats is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final float[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive ints is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final int[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive longs is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final long[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of Objects is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final Object[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive shorts is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final short[] array) {
+ return isArrayEmpty(array);
+ }
+
+ /**
+ * Compares two arrays, using equals(), handling multi-dimensional arrays
+ * correctly.
+ * <p>
+ * Multi-dimensional primitive arrays are also handled correctly by this method.
+ * </p>
+ *
+ * @param array1 the left-hand array to compare, may be {@code null}
+ * @param array2 the right-hand array to compare, may be {@code null}
+ * @return {@code true} if the arrays are equal
+ * @deprecated this method has been replaced by {@code java.util.Objects.deepEquals(Object, Object)} and will be
+ * removed from future releases.
+ */
+ @Deprecated
+ public static boolean isEquals(final Object array1, final Object array2) {
+ return new EqualsBuilder().append(array1, array2).isEquals();
+ }
+
+ /**
+ * Checks if an array of primitive booleans is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final boolean[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive bytes is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final byte[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive chars is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final char[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive doubles is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final double[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive floats is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final float[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive ints is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final int[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive longs is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final long[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of primitive shorts is not empty and not {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static boolean isNotEmpty(final short[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks if an array of Objects is not empty and not {@code null}.
+ *
+ * @param <T> the component type of the array
+ * @param array the array to test
+ * @return {@code true} if the array is not empty and not {@code null}
+ * @since 2.5
+ */
+ public static <T> boolean isNotEmpty(final T[] array) {
+ return !isEmpty(array);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final boolean[] array1, final boolean[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final byte[] array1, final byte[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final char[] array1, final char[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final double[] array1, final double[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final float[] array1, final float[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final int[] array1, final int[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final long[] array1, final long[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ * <p>
+ * Any multi-dimensional aspects of the arrays are ignored.
+ * </p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ * @since 3.11
+ */
+ public static boolean isSameLength(final Object array1, final Object array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ * <p>
+ * Any multi-dimensional aspects of the arrays are ignored.
+ * </p>
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final Object[] array1, final Object[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+
+ /**
+ * Checks whether two arrays are the same length, treating
+ * {@code null} arrays as length {@code 0}.
+ *
+ * @param array1 the first array, may be {@code null}
+ * @param array2 the second array, may be {@code null}
+ * @return {@code true} if length of arrays matches, treating
+ * {@code null} as an empty array
+ */
+ public static boolean isSameLength(final short[] array1, final short[] array2) {
+ return getLength(array1) == getLength(array2);
+ }
+
+ /**
+ * Checks whether two arrays are the same type taking into account
+ * multidimensional arrays.
+ *
+ * @param array1 the first array, must not be {@code null}
+ * @param array2 the second array, must not be {@code null}
+ * @return {@code true} if type of arrays matches
+ * @throws IllegalArgumentException if either array is {@code null}
+ */
+ public static boolean isSameType(final Object array1, final Object array2) {
+ if (array1 == null || array2 == null) {
+ throw new IllegalArgumentException("The Array must not be null");
+ }
+ return array1.getClass().getName().equals(array2.getClass().getName());
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering
+ * ({@code false} before {@code true}).
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final boolean[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ boolean previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final boolean current = array[i];
+ if (BooleanUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final byte[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ byte previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final byte current = array[i];
+ if (NumberUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final char[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ char previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final char current = array[i];
+ if (CharUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final double[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ double previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final double current = array[i];
+ if (Double.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final float[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ float previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final float current = array[i];
+ if (Float.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final int[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ int previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final int current = array[i];
+ if (NumberUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final long[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ long previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final long current = array[i];
+ if (NumberUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to natural ordering.
+ *
+ * @param array the array to check
+ * @return whether the array is sorted according to natural ordering
+ * @since 3.4
+ */
+ public static boolean isSorted(final short[] array) {
+ if (getLength(array) < 2) {
+ return true;
+ }
+
+ short previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final short current = array[i];
+ if (NumberUtils.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to the class's
+ * {@code compareTo} method.
+ *
+ * @param array the array to check
+ * @param <T> the datatype of the array to check, it must implement {@link Comparable}
+ * @return whether the array is sorted
+ * @since 3.4
+ */
+ public static <T extends Comparable<? super T>> boolean isSorted(final T[] array) {
+ return isSorted(array, Comparable::compareTo);
+ }
+
+ /**
+ * This method checks whether the provided array is sorted according to the provided {@link Comparator}.
+ *
+ * @param array the array to check
+ * @param comparator the {@link Comparator} to compare over
+ * @param <T> the datatype of the array
+ * @return whether the array is sorted
+ * @throws NullPointerException if {@code comparator} is {@code null}
+ * @since 3.4
+ */
+ public static <T> boolean isSorted(final T[] array, final Comparator<T> comparator) {
+ Objects.requireNonNull(comparator, "comparator");
+ if (getLength(array) < 2) {
+ return true;
+ }
+ T previous = array[0];
+ final int n = array.length;
+ for (int i = 1; i < n; i++) {
+ final T current = array[i];
+ if (comparator.compare(previous, current) > 0) {
+ return false;
+ }
+
+ previous = current;
+ }
+ return true;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) if
+ * {@code null} array input.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final boolean[] array, final boolean valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than
+ * the array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final boolean[] array, final boolean valueToFind, int startIndex) {
+ if (isEmpty(array) || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final byte[] array, final byte valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final byte[] array, final byte valueToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int lastIndexOf(final char[] array, final char valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ * @since 2.1
+ */
+ public static int lastIndexOf(final char[] array, final char valueToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final double[] array, final double valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value within a given tolerance in the array.
+ * This method will return the index of the last value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to search through for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param tolerance tolerance of the search
+ * @return the index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final double[] array, final double valueToFind, final double tolerance) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE, tolerance);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final double[] array, final double valueToFind, int startIndex) {
+ if (isEmpty(array) || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * This method will return the index of the last value which falls between the region
+ * defined by valueToFind - tolerance and valueToFind + tolerance.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @param tolerance search for value within plus/minus this amount
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final double[] array, final double valueToFind, int startIndex, final double tolerance) {
+ if (isEmpty(array) || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ final double min = valueToFind - tolerance;
+ final double max = valueToFind + tolerance;
+ for (int i = startIndex; i >= 0; i--) {
+ if (array[i] >= min && array[i] <= max) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final float[] array, final float valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final float[] array, final float valueToFind, int startIndex) {
+ if (isEmpty(array) || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final int[] array, final int valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final int[] array, final int valueToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final long[] array, final long valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final long[] array, final long valueToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given object within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @return the last index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final Object[] array, final Object objectToFind) {
+ return lastIndexOf(array, objectToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given object in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than
+ * the array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param objectToFind the object to find, may be {@code null}
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the object within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final Object[] array, final Object objectToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ if (objectToFind == null) {
+ for (int i = startIndex; i >= 0; i--) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else if (array.getClass().getComponentType().isInstance(objectToFind)) {
+ for (int i = startIndex; i >= 0; i--) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the last index of the given value within the array.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to traverse backwards looking for the object, may be {@code null}
+ * @param valueToFind the object to find
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final short[] array, final short valueToFind) {
+ return lastIndexOf(array, valueToFind, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Finds the last index of the given value in the array starting at the given index.
+ * <p>
+ * This method returns {@link #INDEX_NOT_FOUND} ({@code -1}) for a {@code null} input array.
+ * </p>
+ * <p>
+ * A negative startIndex will return {@link #INDEX_NOT_FOUND} ({@code -1}). A startIndex larger than the
+ * array length will search from the end of the array.
+ * </p>
+ *
+ * @param array the array to traverse for looking for the object, may be {@code null}
+ * @param valueToFind the value to find
+ * @param startIndex the start index to traverse backwards from
+ * @return the last index of the value within the array,
+ * {@link #INDEX_NOT_FOUND} ({@code -1}) if not found or {@code null} array input
+ */
+ public static int lastIndexOf(final short[] array, final short valueToFind, int startIndex) {
+ if (array == null || startIndex < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startIndex >= array.length) {
+ startIndex = array.length - 1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (valueToFind == array[i]) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Delegates to {@link Array#newInstance(Class,int)} using generics.
+ *
+ * @param <T> The array type.
+ * @param componentType The array class.
+ * @param length the array length
+ * @return The new array.
+ * @throws NullPointerException if the specified {@code componentType} parameter is null.
+ * @since 3.13.0
+ */
+ @SuppressWarnings("unchecked") // OK, because array and values are of type T
+ public static <T> T[] newInstance(final Class<T> componentType, final int length) {
+ return (T[]) Array.newInstance(componentType, length);
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static boolean[] nullToEmpty(final boolean[] array) {
+ return isEmpty(array) ? EMPTY_BOOLEAN_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Boolean[] nullToEmpty(final Boolean[] array) {
+ return isEmpty(array) ? EMPTY_BOOLEAN_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static byte[] nullToEmpty(final byte[] array) {
+ return isEmpty(array) ? EMPTY_BYTE_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Byte[] nullToEmpty(final Byte[] array) {
+ return isEmpty(array) ? EMPTY_BYTE_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static char[] nullToEmpty(final char[] array) {
+ return isEmpty(array) ? EMPTY_CHAR_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Character[] nullToEmpty(final Character[] array) {
+ return isEmpty(array) ? EMPTY_CHARACTER_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 3.2
+ */
+ public static Class<?>[] nullToEmpty(final Class<?>[] array) {
+ return isEmpty(array) ? EMPTY_CLASS_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static double[] nullToEmpty(final double[] array) {
+ return isEmpty(array) ? EMPTY_DOUBLE_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Double[] nullToEmpty(final Double[] array) {
+ return isEmpty(array) ? EMPTY_DOUBLE_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static float[] nullToEmpty(final float[] array) {
+ return isEmpty(array) ? EMPTY_FLOAT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Float[] nullToEmpty(final Float[] array) {
+ return isEmpty(array) ? EMPTY_FLOAT_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static int[] nullToEmpty(final int[] array) {
+ return isEmpty(array) ? EMPTY_INT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Integer[] nullToEmpty(final Integer[] array) {
+ return isEmpty(array) ? EMPTY_INTEGER_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static long[] nullToEmpty(final long[] array) {
+ return isEmpty(array) ? EMPTY_LONG_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Long[] nullToEmpty(final Long[] array) {
+ return isEmpty(array) ? EMPTY_LONG_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Object[] nullToEmpty(final Object[] array) {
+ return isEmpty(array) ? EMPTY_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static short[] nullToEmpty(final short[] array) {
+ return isEmpty(array) ? EMPTY_SHORT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static Short[] nullToEmpty(final Short[] array) {
+ return isEmpty(array) ? EMPTY_SHORT_OBJECT_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ * <p>
+ * As a memory optimizing technique an empty array passed in will be overridden with
+ * the empty {@code public static} references in this class.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @return the same array, {@code public static} empty array if {@code null} or empty input
+ * @since 2.5
+ */
+ public static String[] nullToEmpty(final String[] array) {
+ return isEmpty(array) ? EMPTY_STRING_ARRAY : array;
+ }
+
+ /**
+ * Defensive programming technique to change a {@code null}
+ * reference to an empty one.
+ * <p>
+ * This method returns an empty array for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to check for {@code null} or empty
+ * @param type the class representation of the desired array
+ * @param <T> the class type
+ * @return the same array, {@code public static} empty array if {@code null}
+ * @throws IllegalArgumentException if the type argument is null
+ * @since 3.5
+ */
+ public static <T> T[] nullToEmpty(final T[] array, final Class<T[]> type) {
+ if (type == null) {
+ throw new IllegalArgumentException("The type must not be null");
+ }
+
+ if (array == null) {
+ return type.cast(Array.newInstance(type.getComponentType(), 0));
+ }
+ return array;
+ }
+
+ private static ThreadLocalRandom random() {
+ return ThreadLocalRandom.current();
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([true], 0) = []
+ * ArrayUtils.remove([true, false], 0) = [false]
+ * ArrayUtils.remove([true, false], 1) = [true]
+ * ArrayUtils.remove([true, true, false], 1) = [true, false]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static boolean[] remove(final boolean[] array, final int index) {
+ return (boolean[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([1, 0], 0) = [0]
+ * ArrayUtils.remove([1, 0], 1) = [1]
+ * ArrayUtils.remove([1, 0, 1], 1) = [1, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static byte[] remove(final byte[] array, final int index) {
+ return (byte[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove(['a'], 0) = []
+ * ArrayUtils.remove(['a', 'b'], 0) = ['b']
+ * ArrayUtils.remove(['a', 'b'], 1) = ['a']
+ * ArrayUtils.remove(['a', 'b', 'c'], 1) = ['a', 'c']
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static char[] remove(final char[] array, final int index) {
+ return (char[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1.1], 0) = []
+ * ArrayUtils.remove([2.5, 6.0], 0) = [6.0]
+ * ArrayUtils.remove([2.5, 6.0], 1) = [2.5]
+ * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static double[] remove(final double[] array, final int index) {
+ return (double[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1.1], 0) = []
+ * ArrayUtils.remove([2.5, 6.0], 0) = [6.0]
+ * ArrayUtils.remove([2.5, 6.0], 1) = [2.5]
+ * ArrayUtils.remove([2.5, 6.0, 3.8], 1) = [2.5, 3.8]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static float[] remove(final float[] array, final int index) {
+ return (float[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static int[] remove(final int[] array, final int index) {
+ return (int[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static long[] remove(final long[] array, final int index) {
+ return (long[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ private static Object remove(final Object array, final int index) {
+ final int length = getLength(array);
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+
+ final Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);
+ System.arraycopy(array, 0, result, 0, index);
+ if (index < length - 1) {
+ System.arraycopy(array, index + 1, result, index, length - index - 1);
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove([1], 0) = []
+ * ArrayUtils.remove([2, 6], 0) = [6]
+ * ArrayUtils.remove([2, 6], 1) = [2]
+ * ArrayUtils.remove([2, 6, 3], 1) = [2, 3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ public static short[] remove(final short[] array, final int index) {
+ return (short[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the element at the specified position from the specified array.
+ * All subsequent elements are shifted to the left (subtracts one from
+ * their indices).
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the element on the specified position. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.remove(["a"], 0) = []
+ * ArrayUtils.remove(["a", "b"], 0) = ["b"]
+ * ArrayUtils.remove(["a", "b"], 1) = ["a"]
+ * ArrayUtils.remove(["a", "b", "c"], 1) = ["a", "c"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param index the position of the element to be removed
+ * @return A new array containing the existing elements except the element
+ * at the specified position.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 2.1
+ */
+ @SuppressWarnings("unchecked") // remove() always creates an array of the same type as its input
+ public static <T> T[] remove(final T[] array, final int index) {
+ return (T[]) remove((Object) array, index);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([true, false, true], 0, 2) = [false]
+ * ArrayUtils.removeAll([true, false, true], 1, 2) = [true]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static boolean[] removeAll(final boolean[] array, final int... indices) {
+ return (boolean[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static byte[] removeAll(final byte[] array, final int... indices) {
+ return (byte[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static char[] removeAll(final char[] array, final int... indices) {
+ return (char[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static double[] removeAll(final double[] array, final int... indices) {
+ return (double[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static float[] removeAll(final float[] array, final int... indices) {
+ return (float[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static int[] removeAll(final int[] array, final int... indices) {
+ return (int[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static long[] removeAll(final long[] array, final int... indices) {
+ return (long[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes multiple array elements specified by indices.
+ *
+ * @param array source
+ * @param indices to remove
+ * @return new array of same type minus elements specified by the set bits in {@code indices}
+ * @since 3.2
+ */
+ // package protected for access by unit tests
+ static Object removeAll(final Object array, final BitSet indices) {
+ if (array == null) {
+ return null;
+ }
+
+ final int srcLength = getLength(array);
+ // No need to check maxIndex here, because method only currently called from removeElements()
+ // which guarantee to generate only valid bit entries.
+// final int maxIndex = indices.length();
+// if (maxIndex > srcLength) {
+// throw new IndexOutOfBoundsException("Index: " + (maxIndex-1) + ", Length: " + srcLength);
+// }
+ final int removals = indices.cardinality(); // true bits are items to remove
+ final Object result = Array.newInstance(array.getClass().getComponentType(), srcLength - removals);
+ int srcIndex = 0;
+ int destIndex = 0;
+ int count;
+ int set;
+ while ((set = indices.nextSetBit(srcIndex)) != -1) {
+ count = set - srcIndex;
+ if (count > 0) {
+ System.arraycopy(array, srcIndex, result, destIndex, count);
+ destIndex += count;
+ }
+ srcIndex = indices.nextClearBit(set);
+ }
+ count = srcLength - srcIndex;
+ if (count > 0) {
+ System.arraycopy(array, srcIndex, result, destIndex, count);
+ }
+ return result;
+ }
+
+ /**
+ * Removes multiple array elements specified by index.
+ *
+ * @param array source
+ * @param indices to remove
+ * @return new array of same type minus elements specified by unique values of {@code indices}
+ * @since 3.0.1
+ */
+ // package protected for access by unit tests
+ static Object removeAll(final Object array, final int... indices) {
+ final int length = getLength(array);
+ int diff = 0; // number of distinct indexes, i.e. number of entries that will be removed
+ final int[] clonedIndices = ArraySorter.sort(clone(indices));
+
+ // identify length of result array
+ if (isNotEmpty(clonedIndices)) {
+ int i = clonedIndices.length;
+ int prevIndex = length;
+ while (--i >= 0) {
+ final int index = clonedIndices[i];
+ if (index < 0 || index >= length) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
+ }
+ if (index >= prevIndex) {
+ continue;
+ }
+ diff++;
+ prevIndex = index;
+ }
+ }
+
+ // create result array
+ final Object result = Array.newInstance(array.getClass().getComponentType(), length - diff);
+ if (diff < length) {
+ int end = length; // index just after last copy
+ int dest = length - diff; // number of entries so far not copied
+ for (int i = clonedIndices.length - 1; i >= 0; i--) {
+ final int index = clonedIndices[i];
+ if (end - index > 1) { // same as (cp > 0)
+ final int cp = end - index - 1;
+ dest -= cp;
+ System.arraycopy(array, index + 1, result, dest, cp);
+ // After this copy, we still have room for dest items.
+ }
+ end = index;
+ }
+ if (end > 0) {
+ System.arraycopy(array, 0, result, 0, end);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll([1], 0) = []
+ * ArrayUtils.removeAll([2, 6], 0) = [6]
+ * ArrayUtils.removeAll([2, 6], 0, 1) = []
+ * ArrayUtils.removeAll([2, 6, 3], 1, 2) = [2]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 2) = [6]
+ * ArrayUtils.removeAll([2, 6, 3], 0, 1, 2) = []
+ * </pre>
+ *
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ public static short[] removeAll(final short[] array, final int... indices) {
+ return (short[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the elements at the specified positions from the specified array.
+ * All remaining elements are shifted to the left.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except those at the specified positions. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <p>
+ * If the input array is {@code null}, an IndexOutOfBoundsException
+ * will be thrown, because in that case no valid index can be specified.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeAll(["a", "b", "c"], 0, 2) = ["b"]
+ * ArrayUtils.removeAll(["a", "b", "c"], 1, 2) = ["a"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may not be {@code null}
+ * @param indices the positions of the elements to be removed
+ * @return A new array containing the existing elements except those
+ * at the specified positions.
+ * @throws IndexOutOfBoundsException if any index is out of range
+ * (index &lt; 0 || index &gt;= array.length), or if the array is {@code null}.
+ * @since 3.0.1
+ */
+ @SuppressWarnings("unchecked") // removeAll() always creates an array of the same type as its input
+ public static <T> T[] removeAll(final T[] array, final int... indices) {
+ return (T[]) removeAll((Object) array, indices);
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified boolean array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(boolean[], boolean)}
+ */
+ @Deprecated
+ public static boolean[] removeAllOccurences(final boolean[] array, final boolean element) {
+ return (boolean[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified byte array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(byte[], byte)}
+ */
+ @Deprecated
+ public static byte[] removeAllOccurences(final byte[] array, final byte element) {
+ return (byte[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified char array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(char[], char)}
+ */
+ @Deprecated
+ public static char[] removeAllOccurences(final char[] array, final char element) {
+ return (char[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified double array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(double[], double)}
+ */
+ @Deprecated
+ public static double[] removeAllOccurences(final double[] array, final double element) {
+ return (double[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified float array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(float[], float)}
+ */
+ @Deprecated
+ public static float[] removeAllOccurences(final float[] array, final float element) {
+ return (float[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified int array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(int[], int)}
+ */
+ @Deprecated
+ public static int[] removeAllOccurences(final int[] array, final int element) {
+ return (int[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified long array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(long[], long)}
+ */
+ @Deprecated
+ public static long[] removeAllOccurences(final long[] array, final long element) {
+ return (long[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified short array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(short[], short)}
+ */
+ @Deprecated
+ public static short[] removeAllOccurences(final short[] array, final short element) {
+ return (short[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param <T> the type of object in the array
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.5
+ * @deprecated Use {@link #removeAllOccurrences(Object[], Object)}
+ */
+ @Deprecated
+ public static <T> T[] removeAllOccurences(final T[] array, final T element) {
+ return (T[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified boolean array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static boolean[] removeAllOccurrences(final boolean[] array, final boolean element) {
+ return (boolean[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified byte array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static byte[] removeAllOccurrences(final byte[] array, final byte element) {
+ return (byte[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified char array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static char[] removeAllOccurrences(final char[] array, final char element) {
+ return (char[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified double array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static double[] removeAllOccurrences(final double[] array, final double element) {
+ return (double[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified float array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static float[] removeAllOccurrences(final float[] array, final float element) {
+ return (float[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified int array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static int[] removeAllOccurrences(final int[] array, final int element) {
+ return (int[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified long array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static long[] removeAllOccurrences(final long[] array, final long element) {
+ return (long[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified short array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static short[] removeAllOccurrences(final short[] array, final short element) {
+ return (short[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the occurrences of the specified element from the specified array.
+ * <p>
+ * All subsequent elements are shifted to the left (subtracts one from their indices).
+ * If the array doesn't contain such an element, no elements are removed from the array.
+ * {@code null} will be returned if the input array is {@code null}.
+ * </p>
+ *
+ * @param <T> the type of object in the array
+ * @param element the element to remove
+ * @param array the input array
+ *
+ * @return A new array containing the existing elements except the occurrences of the specified element.
+ * @since 3.10
+ */
+ public static <T> T[] removeAllOccurrences(final T[] array, final T element) {
+ return (T[]) removeAll((Object) array, indexesOf(array, element));
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, true) = null
+ * ArrayUtils.removeElement([], true) = []
+ * ArrayUtils.removeElement([true], false) = [true]
+ * ArrayUtils.removeElement([true, false], false) = [true]
+ * ArrayUtils.removeElement([true, false, true], true) = [false, true]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static boolean[] removeElement(final boolean[] array, final boolean element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 0) = [1]
+ * ArrayUtils.removeElement([1, 0], 0) = [1]
+ * ArrayUtils.removeElement([1, 0, 1], 1) = [0, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static byte[] removeElement(final byte[] array, final byte element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 'a') = null
+ * ArrayUtils.removeElement([], 'a') = []
+ * ArrayUtils.removeElement(['a'], 'b') = ['a']
+ * ArrayUtils.removeElement(['a', 'b'], 'a') = ['b']
+ * ArrayUtils.removeElement(['a', 'b', 'a'], 'a') = ['b', 'a']
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static char[] removeElement(final char[] array, final char element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1.1) = null
+ * ArrayUtils.removeElement([], 1.1) = []
+ * ArrayUtils.removeElement([1.1], 1.2) = [1.1]
+ * ArrayUtils.removeElement([1.1, 2.3], 1.1) = [2.3]
+ * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static double[] removeElement(final double[] array, final double element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1.1) = null
+ * ArrayUtils.removeElement([], 1.1) = []
+ * ArrayUtils.removeElement([1.1], 1.2) = [1.1]
+ * ArrayUtils.removeElement([1.1, 2.3], 1.1) = [2.3]
+ * ArrayUtils.removeElement([1.1, 2.3, 1.1], 1.1) = [2.3, 1.1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static float[] removeElement(final float[] array, final float element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static int[] removeElement(final int[] array, final int element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static long[] removeElement(final long[] array, final long element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, 1) = null
+ * ArrayUtils.removeElement([], 1) = []
+ * ArrayUtils.removeElement([1], 2) = [1]
+ * ArrayUtils.removeElement([1, 3], 1) = [3]
+ * ArrayUtils.removeElement([1, 3, 1], 1) = [3, 1]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static short[] removeElement(final short[] array, final short element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from the
+ * specified array. All subsequent elements are shifted to the left
+ * (subtracts one from their indices). If the array doesn't contain
+ * such an element, no elements are removed from the array.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except the first occurrence of the specified element. The component
+ * type of the returned array is always the same as that of the input
+ * array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElement(null, "a") = null
+ * ArrayUtils.removeElement([], "a") = []
+ * ArrayUtils.removeElement(["a"], "b") = ["a"]
+ * ArrayUtils.removeElement(["a", "b"], "a") = ["b"]
+ * ArrayUtils.removeElement(["a", "b", "a"], "a") = ["b", "a"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may be {@code null}
+ * @param element the element to be removed
+ * @return A new array containing the existing elements except the first
+ * occurrence of the specified element.
+ * @since 2.1
+ */
+ public static <T> T[] removeElement(final T[] array, final Object element) {
+ final int index = indexOf(array, element);
+ return index == INDEX_NOT_FOUND ? clone(array) : remove(array, index);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, true, false) = null
+ * ArrayUtils.removeElements([], true, false) = []
+ * ArrayUtils.removeElements([true], false, false) = [true]
+ * ArrayUtils.removeElements([true, false], true, true) = [false]
+ * ArrayUtils.removeElements([true, false, true], true) = [false, true]
+ * ArrayUtils.removeElements([true, false, true], true, true) = [false]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static boolean[] removeElements(final boolean[] array, final boolean... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Boolean, MutableInt> occurrences = new HashMap<>(2); // only two possible values here
+ for (final boolean v : values) {
+ final Boolean boxed = Boolean.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final boolean key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (boolean[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static byte[] removeElements(final byte[] array, final byte... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final Map<Byte, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final byte v : values) {
+ final Byte boxed = Byte.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final byte key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (byte[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static char[] removeElements(final char[] array, final char... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Character, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final char v : values) {
+ final Character boxed = Character.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final char key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (char[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static double[] removeElements(final double[] array, final double... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Double, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final double v : values) {
+ final Double boxed = Double.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final double key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (double[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static float[] removeElements(final float[] array, final float... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Float, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final float v : values) {
+ final Float boxed = Float.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final float key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (float[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static int[] removeElements(final int[] array, final int... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Integer, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final int v : values) {
+ final Integer boxed = Integer.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final int key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (int[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static long[] removeElements(final long[] array, final long... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Long, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final long v : values) {
+ final Long boxed = Long.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final long key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (long[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, 1, 2) = null
+ * ArrayUtils.removeElements([], 1, 2) = []
+ * ArrayUtils.removeElements([1], 2, 3) = [1]
+ * ArrayUtils.removeElements([1, 3], 1, 2) = [3]
+ * ArrayUtils.removeElements([1, 3, 1], 1) = [3, 1]
+ * ArrayUtils.removeElements([1, 3, 1], 1, 1) = [3]
+ * </pre>
+ *
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ public static short[] removeElements(final short[] array, final short... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<Short, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final short v : values) {
+ final Short boxed = Short.valueOf(v);
+ final MutableInt count = occurrences.get(boxed);
+ if (count == null) {
+ occurrences.put(boxed, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final short key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ return (short[]) removeAll(array, toRemove);
+ }
+
+ /**
+ * Removes occurrences of specified elements, in specified quantities,
+ * from the specified array. All subsequent elements are shifted left.
+ * For any element-to-be-removed specified in greater quantities than
+ * contained in the original array, no change occurs beyond the
+ * removal of the existing matching items.
+ * <p>
+ * This method returns a new array with the same elements of the input
+ * array except for the earliest-encountered occurrences of the specified
+ * elements. The component type of the returned array is always the same
+ * as that of the input array.
+ * </p>
+ * <pre>
+ * ArrayUtils.removeElements(null, "a", "b") = null
+ * ArrayUtils.removeElements([], "a", "b") = []
+ * ArrayUtils.removeElements(["a"], "b", "c") = ["a"]
+ * ArrayUtils.removeElements(["a", "b"], "a", "c") = ["b"]
+ * ArrayUtils.removeElements(["a", "b", "a"], "a") = ["b", "a"]
+ * ArrayUtils.removeElements(["a", "b", "a"], "a", "a") = ["b"]
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array to remove the element from, may be {@code null}
+ * @param values the elements to be removed
+ * @return A new array containing the existing elements except the
+ * earliest-encountered occurrences of the specified elements.
+ * @since 3.0.1
+ */
+ @SafeVarargs
+ public static <T> T[] removeElements(final T[] array, final T... values) {
+ if (isEmpty(array) || isEmpty(values)) {
+ return clone(array);
+ }
+ final HashMap<T, MutableInt> occurrences = new HashMap<>(values.length);
+ for (final T v : values) {
+ final MutableInt count = occurrences.get(v);
+ if (count == null) {
+ occurrences.put(v, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ final BitSet toRemove = new BitSet();
+ for (int i = 0; i < array.length; i++) {
+ final T key = array[i];
+ final MutableInt count = occurrences.get(key);
+ if (count != null) {
+ if (count.decrementAndGet() == 0) {
+ occurrences.remove(key);
+ }
+ toRemove.set(i);
+ }
+ }
+ @SuppressWarnings("unchecked") // removeAll() always creates an array of the same type as its input
+ final T[] result = (T[]) removeAll(array, toRemove);
+ return result;
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final boolean[] array) {
+ if (array == null) {
+ return;
+ }
+ reverse(array, 0, array.length);
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final boolean[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ boolean tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final byte[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final byte[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ byte tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final char[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final char[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ char tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final double[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final double[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ double tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final float[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final float[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ float tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final int[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final int[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ int tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final long[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final long[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ long tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * There is no special handling for multi-dimensional arrays.
+ * </p>
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final Object[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Under value (&lt;0) is promoted to 0, over value (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Under value (&lt; start index) results in no
+ * change. Over value (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final Object[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ Object tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Reverses the order of the given array.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array the array to reverse, may be {@code null}
+ */
+ public static void reverse(final short[] array) {
+ if (array != null) {
+ reverse(array, 0, array.length);
+ }
+ }
+
+ /**
+ * Reverses the order of the given array in the given range.
+ * <p>
+ * This method does nothing for a {@code null} input array.
+ * </p>
+ *
+ * @param array
+ * the array to reverse, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are reversed in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @since 3.2
+ */
+ public static void reverse(final short[] array, final int startIndexInclusive, final int endIndexExclusive) {
+ if (array == null) {
+ return;
+ }
+ int i = Math.max(startIndexInclusive, 0);
+ int j = Math.min(array.length, endIndexExclusive) - 1;
+ short tmp;
+ while (j > i) {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ /**
+ * Sets all elements of the specified array, using the provided generator supplier to compute each element.
+ * <p>
+ * If the generator supplier throws an exception, it is relayed to the caller and the array is left in an indeterminate
+ * state.
+ * </p>
+ *
+ * @param <T> type of elements of the array.
+ * @param array array to be initialized.
+ * @param generator a function accepting an index and producing the desired value for that position.
+ * @return the input array
+ * @since 3.13.0
+ */
+ public static <T> T[] setAll(final T[] array, final IntFunction<? extends T> generator) {
+ if (array != null && generator != null) {
+ Arrays.setAll(array, generator);
+ }
+ return array;
+ }
+
+ /**
+ * Sets all elements of the specified array, using the provided generator supplier to compute each element.
+ * <p>
+ * If the generator supplier throws an exception, it is relayed to the caller and the array is left in an indeterminate
+ * state.
+ * </p>
+ *
+ * @param <T> type of elements of the array.
+ * @param array array to be initialized.
+ * @param generator a function accepting an index and producing the desired value for that position.
+ * @return the input array
+ * @since 3.13.0
+ */
+ public static <T> T[] setAll(final T[] array, final Supplier<? extends T> generator) {
+ if (array != null && generator != null) {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = generator.get();
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Shifts the order of the given boolean array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final boolean[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given boolean array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final boolean[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given byte array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final byte[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given byte array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final byte[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given char array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final char[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given char array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final char[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given double array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final double[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given double array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final double[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given float array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final float[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given float array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final float[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given int array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final int[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given int array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final int[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given long array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final long[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given long array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final long[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final Object[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final Object[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Shifts the order of the given short array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array the array to shift, may be {@code null}
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final short[] array, final int offset) {
+ if (array != null) {
+ shift(array, 0, array.length, offset);
+ }
+ }
+
+ /**
+ * Shifts the order of a series of elements in the given short array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for {@code null} or empty input arrays.</p>
+ *
+ * @param array
+ * the array to shift, may be {@code null}
+ * @param startIndexInclusive
+ * the starting index. Undervalue (&lt;0) is promoted to 0, overvalue (&gt;array.length) results in no
+ * change.
+ * @param endIndexExclusive
+ * elements up to endIndex-1 are shifted in the array. Undervalue (&lt; start index) results in no
+ * change. Overvalue (&gt;array.length) is demoted to array length.
+ * @param offset
+ * The number of positions to rotate the elements. If the offset is larger than the number of elements to
+ * rotate, than the effective offset is modulo the number of elements to rotate.
+ * @since 3.5
+ */
+ public static void shift(final short[] array, int startIndexInclusive, int endIndexExclusive, int offset) {
+ if (array == null || startIndexInclusive >= array.length - 1 || endIndexExclusive <= 0) {
+ return;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive >= array.length) {
+ endIndexExclusive = array.length;
+ }
+ int n = endIndexExclusive - startIndexInclusive;
+ if (n <= 1) {
+ return;
+ }
+ offset %= n;
+ if (offset < 0) {
+ offset += n;
+ }
+ // For algorithm explanations and proof of O(n) time complexity and O(1) space complexity
+ // see https://beradrian.wordpress.com/2015/04/07/shift-an-array-in-on-in-place/
+ while (n > 1 && offset > 0) {
+ final int n_offset = n - offset;
+
+ if (offset > n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n - n_offset, n_offset);
+ n = offset;
+ offset -= n_offset;
+ } else if (offset < n_offset) {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ startIndexInclusive += offset;
+ n = n_offset;
+ } else {
+ swap(array, startIndexInclusive, startIndexInclusive + n_offset, offset);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final boolean[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final boolean[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final byte[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final byte[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final char[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final char[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final double[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final double[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final float[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final float[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final int[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final int[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final long[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final long[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final Object[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final Object[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final short[] array) {
+ shuffle(array, random());
+ }
+
+ /**
+ * Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
+ *
+ * @param array the array to shuffle
+ * @param random the source of randomness used to permute the elements
+ * @see <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle algorithm</a>
+ * @since 3.6
+ */
+ public static void shuffle(final short[] array, final Random random) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, random.nextInt(i), 1);
+ }
+ }
+
+ /**
+ * Produces a new {@code boolean} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(boolean[], int, int)
+ */
+ public static boolean[] subarray(final boolean[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+
+ final boolean[] subarray = new boolean[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code byte} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(byte[], int, int)
+ */
+ public static byte[] subarray(final byte[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ final byte[] subarray = new byte[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code char} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(char[], int, int)
+ */
+ public static char[] subarray(final char[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+
+ final char[] subarray = new char[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code double} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(double[], int, int)
+ */
+ public static double[] subarray(final double[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+
+ final double[] subarray = new double[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code float} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(float[], int, int)
+ */
+ public static float[] subarray(final float[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+
+ final float[] subarray = new float[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code int} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(int[], int, int)
+ */
+ public static int[] subarray(final int[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_INT_ARRAY;
+ }
+
+ final int[] subarray = new int[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code long} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(long[], int, int)
+ */
+ public static long[] subarray(final long[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+
+ final long[] subarray = new long[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new {@code short} array containing the elements
+ * between the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ *
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(short[], int, int)
+ */
+ public static short[] subarray(final short[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ if (newSize <= 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+
+ final short[] subarray = new short[newSize];
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Produces a new array containing the elements between
+ * the start and end indices.
+ * <p>
+ * The start index is inclusive, the end index exclusive.
+ * Null array input produces null output.
+ * </p>
+ * <p>
+ * The component type of the subarray is always the same as
+ * that of the input array. Thus, if the input is an array of type
+ * {@link Date}, the following usage is envisaged:
+ * </p>
+ * <pre>
+ * Date[] someDates = (Date[]) ArrayUtils.subarray(allDates, 2, 5);
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param array the array
+ * @param startIndexInclusive the starting index. Undervalue (&lt;0)
+ * is promoted to 0, overvalue (&gt;array.length) results
+ * in an empty array.
+ * @param endIndexExclusive elements up to endIndex-1 are present in the
+ * returned subarray. Undervalue (&lt; startIndex) produces
+ * empty array, overvalue (&gt;array.length) is demoted to
+ * array length.
+ * @return a new array containing the elements between
+ * the start and end indices.
+ * @since 2.1
+ * @see Arrays#copyOfRange(Object[], int, int)
+ */
+ public static <T> T[] subarray(final T[] array, int startIndexInclusive, int endIndexExclusive) {
+ if (array == null) {
+ return null;
+ }
+ if (startIndexInclusive < 0) {
+ startIndexInclusive = 0;
+ }
+ if (endIndexExclusive > array.length) {
+ endIndexExclusive = array.length;
+ }
+ final int newSize = endIndexExclusive - startIndexInclusive;
+ final Class<T> type = getComponentType(array);
+ if (newSize <= 0) {
+ return newInstance(type, 0);
+ }
+ final T[] subarray = newInstance(type, newSize);
+ System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
+ return subarray;
+ }
+
+ /**
+ * Swaps two elements in the given boolean array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final boolean[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+
+ /**
+ * Swaps a series of elements in the given boolean array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([true, false, true, false], 0, 2, 1) -&gt; [true, false, true, false]</li>
+ * <li>ArrayUtils.swap([true, false, true, false], 0, 0, 1) -&gt; [true, false, true, false]</li>
+ * <li>ArrayUtils.swap([true, false, true, false], 0, 2, 2) -&gt; [true, false, true, false]</li>
+ * <li>ArrayUtils.swap([true, false, true, false], -3, 2, 2) -&gt; [true, false, true, false]</li>
+ * <li>ArrayUtils.swap([true, false, true, false], 0, 3, 3) -&gt; [false, false, true, true]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final boolean[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final boolean aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given byte array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final byte[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given byte array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final byte[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final byte aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given char array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final char[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given char array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final char[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final char aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given double array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final double[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given double array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final double[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final double aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given float array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final float[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given float array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final float[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final float aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+
+ }
+
+ /**
+ * Swaps two elements in the given int array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final int[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given int array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final int[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final int aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given long array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([true, false, true], 0, 2) -&gt; [true, false, true]</li>
+ * <li>ArrayUtils.swap([true, false, true], 0, 0) -&gt; [true, false, true]</li>
+ * <li>ArrayUtils.swap([true, false, true], 1, 0) -&gt; [false, true, true]</li>
+ * <li>ArrayUtils.swap([true, false, true], 0, 5) -&gt; [true, false, true]</li>
+ * <li>ArrayUtils.swap([true, false, true], -1, 1) -&gt; [false, true, true]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final long[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given long array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final long[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final long aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap(["1", "2", "3"], 0, 2) -&gt; ["3", "2", "1"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3"], 0, 0) -&gt; ["1", "2", "3"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3"], 1, 0) -&gt; ["2", "1", "3"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3"], 0, 5) -&gt; ["1", "2", "3"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3"], -1, 1) -&gt; ["2", "1", "3"]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final Object[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap(["1", "2", "3", "4"], 0, 2, 1) -&gt; ["3", "2", "1", "4"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3", "4"], 0, 0, 1) -&gt; ["1", "2", "3", "4"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3", "4"], 2, 0, 2) -&gt; ["3", "4", "1", "2"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3", "4"], -3, 2, 2) -&gt; ["3", "4", "1", "2"]</li>
+ * <li>ArrayUtils.swap(["1", "2", "3", "4"], 0, 3, 3) -&gt; ["4", "2", "3", "1"]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final Object[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final Object aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Swaps two elements in the given short array.
+ *
+ * <p>There is no special handling for multi-dimensional arrays. This method
+ * does nothing for a {@code null} or empty input array or for overflow indices.
+ * Negative indices are promoted to 0(zero).</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 2) -&gt; [3, 2, 1]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 0) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 1, 0) -&gt; [2, 1, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], 0, 5) -&gt; [1, 2, 3]</li>
+ * <li>ArrayUtils.swap([1, 2, 3], -1, 1) -&gt; [2, 1, 3]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element to swap
+ * @param offset2 the index of the second element to swap
+ * @since 3.5
+ */
+ public static void swap(final short[] array, final int offset1, final int offset2) {
+ swap(array, offset1, offset2, 1);
+ }
+
+ /**
+ * Swaps a series of elements in the given short array.
+ *
+ * <p>This method does nothing for a {@code null} or empty input array or
+ * for overflow indices. Negative indices are promoted to 0(zero). If any
+ * of the sub-arrays to swap falls outside of the given array, then the
+ * swap is stopped at the end of the array and as many as possible elements
+ * are swapped.</p>
+ *
+ * Examples:
+ * <ul>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 2, 1) -&gt; [3, 2, 1, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 0, 1) -&gt; [1, 2, 3, 4]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 2, 0, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], -3, 2, 2) -&gt; [3, 4, 1, 2]</li>
+ * <li>ArrayUtils.swap([1, 2, 3, 4], 0, 3, 3) -&gt; [4, 2, 3, 1]</li>
+ * </ul>
+ *
+ * @param array the array to swap, may be {@code null}
+ * @param offset1 the index of the first element in the series to swap
+ * @param offset2 the index of the second element in the series to swap
+ * @param len the number of elements to swap starting with the given indices
+ * @since 3.5
+ */
+ public static void swap(final short[] array, int offset1, int offset2, int len) {
+ if (isEmpty(array) || offset1 >= array.length || offset2 >= array.length) {
+ return;
+ }
+ if (offset1 < 0) {
+ offset1 = 0;
+ }
+ if (offset2 < 0) {
+ offset2 = 0;
+ }
+ if (offset1 == offset2) {
+ return;
+ }
+ len = Math.min(Math.min(len, array.length - offset1), array.length - offset2);
+ for (int i = 0; i < len; i++, offset1++, offset2++) {
+ final short aux = array[offset1];
+ array[offset1] = array[offset2];
+ array[offset2] = aux;
+ }
+ }
+
+ /**
+ * Create a type-safe generic array.
+ * <p>
+ * The Java language does not allow an array to be created from a generic type:
+ * </p>
+ * <pre>
+ public static &lt;T&gt; T[] createAnArray(int size) {
+ return new T[size]; // compiler error here
+ }
+ public static &lt;T&gt; T[] createAnArray(int size) {
+ return (T[]) new Object[size]; // ClassCastException at runtime
+ }
+ * </pre>
+ * <p>
+ * Therefore new arrays of generic types can be created with this method.
+ * For example, an array of Strings can be created:
+ * </p>
+ * <pre>
+ String[] array = ArrayUtils.toArray("1", "2");
+ String[] emptyArray = ArrayUtils.&lt;String&gt;toArray();
+ * </pre>
+ * <p>
+ * The method is typically used in scenarios, where the caller itself uses generic types
+ * that have to be combined into an array.
+ * </p>
+ * <p>
+ * Note, this method makes only sense to provide arguments of the same type so that the
+ * compiler can deduce the type of the array itself. While it is possible to select the
+ * type explicitly like in
+ * {@code Number[] array = ArrayUtils.&lt;Number&gt;toArray(Integer.valueOf(42), Double.valueOf(Math.PI))},
+ * there is no real advantage when compared to
+ * {@code new Number[] {Integer.valueOf(42), Double.valueOf(Math.PI)}}.
+ * </p>
+ *
+ * @param <T> the array's element type
+ * @param items the varargs array items, null allowed
+ * @return the array, not null unless a null array is passed in
+ * @since 3.0
+ */
+ public static <T> T[] toArray(@SuppressWarnings("unchecked") final T... items) {
+ return items;
+ }
+
+ /**
+ * Converts the given array into a {@link java.util.Map}. Each element of the array
+ * must be either a {@link java.util.Map.Entry} or an Array, containing at least two
+ * elements, where the first element is used as key and the second as
+ * value.
+ * <p>
+ * This method can be used to initialize:
+ * </p>
+ * <pre>
+ * // Create a Map mapping colors.
+ * Map colorMap = ArrayUtils.toMap(new String[][] {
+ * {"RED", "#FF0000"},
+ * {"GREEN", "#00FF00"},
+ * {"BLUE", "#0000FF"}});
+ * </pre>
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array an array whose elements are either a {@link java.util.Map.Entry} or
+ * an Array containing at least two elements, may be {@code null}
+ * @return a {@link Map} that was created from the array
+ * @throws IllegalArgumentException if one element of this Array is
+ * itself an Array containing less than two elements
+ * @throws IllegalArgumentException if the array contains elements other
+ * than {@link java.util.Map.Entry} and an Array
+ */
+ public static Map<Object, Object> toMap(final Object[] array) {
+ if (array == null) {
+ return null;
+ }
+ final Map<Object, Object> map = new HashMap<>((int) (array.length * 1.5));
+ for (int i = 0; i < array.length; i++) {
+ final Object object = array[i];
+ if (object instanceof Map.Entry<?, ?>) {
+ final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
+ map.put(entry.getKey(), entry.getValue());
+ } else if (object instanceof Object[]) {
+ final Object[] entry = (Object[]) object;
+ if (entry.length < 2) {
+ throw new IllegalArgumentException("Array element " + i + ", '"
+ + object
+ + "', has a length less than 2");
+ }
+ map.put(entry[0], entry[1]);
+ } else {
+ throw new IllegalArgumentException("Array element " + i + ", '"
+ + object
+ + "', is neither of type Map.Entry nor an Array");
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Converts an array of primitive booleans to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code boolean} array
+ * @return a {@link Boolean} array, {@code null} if null array input
+ */
+ public static Boolean[] toObject(final boolean[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_BOOLEAN_OBJECT_ARRAY;
+ }
+ final Boolean[] result = new Boolean[array.length];
+ return setAll(result, i -> array[i] ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * Converts an array of primitive bytes to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code byte} array
+ * @return a {@link Byte} array, {@code null} if null array input
+ */
+ public static Byte[] toObject(final byte[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_BYTE_OBJECT_ARRAY;
+ }
+ return setAll(new Byte[array.length], i -> Byte.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive chars to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code char} array
+ * @return a {@link Character} array, {@code null} if null array input
+ */
+ public static Character[] toObject(final char[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_CHARACTER_OBJECT_ARRAY;
+ }
+ return setAll(new Character[array.length], i -> Character.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive doubles to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code double} array
+ * @return a {@link Double} array, {@code null} if null array input
+ */
+ public static Double[] toObject(final double[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_DOUBLE_OBJECT_ARRAY;
+ }
+ return setAll(new Double[array.length], i -> Double.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive floats to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code float} array
+ * @return a {@link Float} array, {@code null} if null array input
+ */
+ public static Float[] toObject(final float[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_FLOAT_OBJECT_ARRAY;
+ }
+ return setAll(new Float[array.length], i -> Float.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive ints to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array an {@code int} array
+ * @return an {@link Integer} array, {@code null} if null array input
+ */
+ public static Integer[] toObject(final int[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_INTEGER_OBJECT_ARRAY;
+ }
+ return setAll(new Integer[array.length], i -> Integer.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive longs to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code long} array
+ * @return a {@link Long} array, {@code null} if null array input
+ */
+ public static Long[] toObject(final long[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_LONG_OBJECT_ARRAY;
+ }
+ return setAll(new Long[array.length], i -> Long.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of primitive shorts to objects.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.</p>
+ *
+ * @param array a {@code short} array
+ * @return a {@link Short} array, {@code null} if null array input
+ */
+ public static Short[] toObject(final short[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_SHORT_OBJECT_ARRAY;
+ }
+ return setAll(new Short[array.length], i -> Short.valueOf(array[i]));
+ }
+
+ /**
+ * Converts an array of object Booleans to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ * <p>
+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false.
+ * </p>
+ *
+ * @param array a {@link Boolean} array, may be {@code null}
+ * @return a {@code boolean} array, {@code null} if null array input
+ */
+ public static boolean[] toPrimitive(final Boolean[] array) {
+ return toPrimitive(array, false);
+ }
+
+ /**
+ * Converts an array of object Booleans to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Boolean} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code boolean} array, {@code null} if null array input
+ */
+ public static boolean[] toPrimitive(final Boolean[] array, final boolean valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_BOOLEAN_ARRAY;
+ }
+ final boolean[] result = new boolean[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Boolean b = array[i];
+ result[i] = b == null ? valueForNull : b.booleanValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Bytes to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Byte} array, may be {@code null}
+ * @return a {@code byte} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static byte[] toPrimitive(final Byte[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ final byte[] result = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].byteValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Bytes to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Byte} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code byte} array, {@code null} if null array input
+ */
+ public static byte[] toPrimitive(final Byte[] array, final byte valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ final byte[] result = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Byte b = array[i];
+ result[i] = b == null ? valueForNull : b.byteValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Characters to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Character} array, may be {@code null}
+ * @return a {@code char} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static char[] toPrimitive(final Character[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ final char[] result = new char[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].charValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Character to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Character} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code char} array, {@code null} if null array input
+ */
+ public static char[] toPrimitive(final Character[] array, final char valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ final char[] result = new char[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Character b = array[i];
+ result[i] = b == null ? valueForNull : b.charValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Doubles to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Double} array, may be {@code null}
+ * @return a {@code double} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static double[] toPrimitive(final Double[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ final double[] result = new double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].doubleValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Doubles to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Double} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code double} array, {@code null} if null array input
+ */
+ public static double[] toPrimitive(final Double[] array, final double valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ final double[] result = new double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Double b = array[i];
+ result[i] = b == null ? valueForNull : b.doubleValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Floats to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Float} array, may be {@code null}
+ * @return a {@code float} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static float[] toPrimitive(final Float[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+ final float[] result = new float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].floatValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Floats to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Float} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code float} array, {@code null} if null array input
+ */
+ public static float[] toPrimitive(final Float[] array, final float valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_FLOAT_ARRAY;
+ }
+ final float[] result = new float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Float b = array[i];
+ result[i] = b == null ? valueForNull : b.floatValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Integers to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Integer} array, may be {@code null}
+ * @return an {@code int} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static int[] toPrimitive(final Integer[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ final int[] result = new int[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].intValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Integer to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Integer} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return an {@code int} array, {@code null} if null array input
+ */
+ public static int[] toPrimitive(final Integer[] array, final int valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ final int[] result = new int[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Integer b = array[i];
+ result[i] = b == null ? valueForNull : b.intValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Longs to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Long} array, may be {@code null}
+ * @return a {@code long} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static long[] toPrimitive(final Long[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+ final long[] result = new long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].longValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Long to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Long} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code long} array, {@code null} if null array input
+ */
+ public static long[] toPrimitive(final Long[] array, final long valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_LONG_ARRAY;
+ }
+ final long[] result = new long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Long b = array[i];
+ result[i] = b == null ? valueForNull : b.longValue();
+ }
+ return result;
+ }
+
+ /**
+ * Create an array of primitive type from an array of wrapper types.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array an array of wrapper object
+ * @return an array of the corresponding primitive type, or the original array
+ * @since 3.5
+ */
+ public static Object toPrimitive(final Object array) {
+ if (array == null) {
+ return null;
+ }
+ final Class<?> ct = array.getClass().getComponentType();
+ final Class<?> pt = ClassUtils.wrapperToPrimitive(ct);
+ if (Boolean.TYPE.equals(pt)) {
+ return toPrimitive((Boolean[]) array);
+ }
+ if (Character.TYPE.equals(pt)) {
+ return toPrimitive((Character[]) array);
+ }
+ if (Byte.TYPE.equals(pt)) {
+ return toPrimitive((Byte[]) array);
+ }
+ if (Integer.TYPE.equals(pt)) {
+ return toPrimitive((Integer[]) array);
+ }
+ if (Long.TYPE.equals(pt)) {
+ return toPrimitive((Long[]) array);
+ }
+ if (Short.TYPE.equals(pt)) {
+ return toPrimitive((Short[]) array);
+ }
+ if (Double.TYPE.equals(pt)) {
+ return toPrimitive((Double[]) array);
+ }
+ if (Float.TYPE.equals(pt)) {
+ return toPrimitive((Float[]) array);
+ }
+ return array;
+ }
+
+ /**
+ * Converts an array of object Shorts to primitives.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Short} array, may be {@code null}
+ * @return a {@code byte} array, {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ */
+ public static short[] toPrimitive(final Short[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+ final short[] result = new short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].shortValue();
+ }
+ return result;
+ }
+
+ /**
+ * Converts an array of object Short to primitives handling {@code null}.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array a {@link Short} array, may be {@code null}
+ * @param valueForNull the value to insert if {@code null} found
+ * @return a {@code byte} array, {@code null} if null array input
+ */
+ public static short[] toPrimitive(final Short[] array, final short valueForNull) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_SHORT_ARRAY;
+ }
+ final short[] result = new short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Short b = array[i];
+ result[i] = b == null ? valueForNull : b.shortValue();
+ }
+ return result;
+ }
+
+ /**
+ * Outputs an array as a String, treating {@code null} as an empty array.
+ * <p>
+ * Multi-dimensional arrays are handled correctly, including
+ * multi-dimensional primitive arrays.
+ * </p>
+ * <p>
+ * The format is that of Java source code, for example {@code {a,b}}.
+ * </p>
+ *
+ * @param array the array to get a toString for, may be {@code null}
+ * @return a String representation of the array, '{}' if null array input
+ */
+ public static String toString(final Object array) {
+ return toString(array, "{}");
+ }
+
+ /**
+ * Outputs an array as a String handling {@code null}s.
+ * <p>
+ * Multi-dimensional arrays are handled correctly, including
+ * multi-dimensional primitive arrays.
+ * </p>
+ * <p>
+ * The format is that of Java source code, for example {@code {a,b}}.
+ * </p>
+ *
+ * @param array the array to get a toString for, may be {@code null}
+ * @param stringIfNull the String to return if the array is {@code null}
+ * @return a String representation of the array
+ */
+ public static String toString(final Object array, final String stringIfNull) {
+ if (array == null) {
+ return stringIfNull;
+ }
+ return new ToStringBuilder(array, ToStringStyle.SIMPLE_STYLE).append(array).toString();
+ }
+
+ /**
+ * Returns an array containing the string representation of each element in the argument array.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the {@code Object[]} to be processed, may be null
+ * @return {@code String[]} of the same size as the source with its element's string representation,
+ * {@code null} if null array input
+ * @throws NullPointerException if an array element is {@code null}
+ * @since 3.6
+ */
+ public static String[] toStringArray(final Object[] array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_STRING_ARRAY;
+ }
+
+ final String[] result = new String[array.length];
+ for (int i = 0; i < array.length; i++) {
+ result[i] = array[i].toString();
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns an array containing the string representation of each element in the argument
+ * array handling {@code null} elements.
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array the Object[] to be processed, may be null
+ * @param valueForNullElements the value to insert if {@code null} is found
+ * @return a {@link String} array, {@code null} if null array input
+ * @since 3.6
+ */
+ public static String[] toStringArray(final Object[] array, final String valueForNullElements) {
+ if (null == array) {
+ return null;
+ }
+ if (array.length == 0) {
+ return EMPTY_STRING_ARRAY;
+ }
+
+ final String[] result = new String[array.length];
+ for (int i = 0; i < array.length; i++) {
+ final Object object = array[i];
+ result[i] = object == null ? valueForNullElements : object.toString();
+ }
+
+ return result;
+ }
+
+ /**
+ * ArrayUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code ArrayUtils.clone(new int[] {2})}.
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance
+ * to operate.
+ * </p>
+ */
+ public ArrayUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/BitField.java b/src/main/java/org/apache/commons/lang3/BitField.java
new file mode 100644
index 000000000..a2d0ccbe3
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/BitField.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+/**
+ * Supports operations on bit-mapped fields. Instances of this class can be
+ * used to store a flag or data within an {@code int}, {@code short} or
+ * {@code byte}.
+ *
+ * <p>Each {@link BitField} is constructed with a mask value, which indicates
+ * the bits that will be used to store and retrieve the data for that field.
+ * For instance, the mask {@code 0xFF} indicates the least-significant byte
+ * should be used to store the data.</p>
+ *
+ * <p>As an example, consider a car painting machine that accepts
+ * paint instructions as integers. Bit fields can be used to encode this:</p>
+ *
+ *<pre>
+ * // blue, green and red are 1 byte values (0-255) stored in the three least
+ * // significant bytes
+ * BitField blue = new BitField(0xFF);
+ * BitField green = new BitField(0xFF00);
+ * BitField red = new BitField(0xFF0000);
+ *
+ * // anyColor is a flag triggered if any color is used
+ * BitField anyColor = new BitField(0xFFFFFF);
+ *
+ * // isMetallic is a single bit flag
+ * BitField isMetallic = new BitField(0x1000000);
+ *</pre>
+ *
+ * <p>Using these {@link BitField} instances, a paint instruction can be
+ * encoded into an integer:</p>
+ *
+ *<pre>
+ * int paintInstruction = 0;
+ * paintInstruction = red.setValue(paintInstruction, 35);
+ * paintInstruction = green.setValue(paintInstruction, 100);
+ * paintInstruction = blue.setValue(paintInstruction, 255);
+ *</pre>
+ *
+ * <p>Flags and data can be retrieved from the integer:</p>
+ *
+ *<pre>
+ * // Prints true if red, green or blue is non-zero
+ * System.out.println(anyColor.isSet(paintInstruction)); // prints true
+ *
+ * // Prints value of red, green and blue
+ * System.out.println(red.getValue(paintInstruction)); // prints 35
+ * System.out.println(green.getValue(paintInstruction)); // prints 100
+ * System.out.println(blue.getValue(paintInstruction)); // prints 255
+ *
+ * // Prints true if isMetallic was set
+ * System.out.println(isMetallic.isSet(paintInstruction)); // prints false
+ *</pre>
+ *
+ * @since 2.0
+ */
+public class BitField {
+
+ private final int mask;
+ private final int shiftCount;
+
+ /**
+ * Creates a BitField instance.
+ *
+ * @param mask the mask specifying which bits apply to this
+ * BitField. Bits that are set in this mask are the bits
+ * that this BitField operates on
+ */
+ public BitField(final int mask) {
+ this.mask = mask;
+ this.shiftCount = mask == 0 ? 0 : Integer.numberOfTrailingZeros(mask);
+ }
+
+ /**
+ * Obtains the value for the specified BitField, appropriately
+ * shifted right.
+ *
+ * <p>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).</p>
+ *
+ * @see #setValue(int,int)
+ * @param holder the int data containing the bits we're interested
+ * in
+ * @return the selected bits, shifted right appropriately
+ */
+ public int getValue(final int holder) {
+ return getRawValue(holder) >> shiftCount;
+ }
+
+ /**
+ * Obtains the value for the specified BitField, appropriately
+ * shifted right, as a short.
+ *
+ * <p>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).</p>
+ *
+ * @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, unshifted.
+ *
+ * @param holder the int data containing the bits we're
+ * interested in
+ * @return the selected bits
+ */
+ public int getRawValue(final int holder) {
+ return holder & mask;
+ }
+
+ /**
+ * Obtains the value for the specified BitField, unshifted.
+ *
+ * @param holder the short data containing the bits we're
+ * interested in
+ * @return the selected bits
+ */
+ public short getShortRawValue(final short holder) {
+ return (short) getRawValue(holder);
+ }
+
+ /**
+ * Returns whether the field is set or not.
+ *
+ * <p>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.</p>
+ *
+ * @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 boolean isSet(final int holder) {
+ return (holder & mask) != 0;
+ }
+
+ /**
+ * Returns whether all of the bits are set or not.
+ *
+ * <p>This is a stricter test than {@link #isSet(int)},
+ * in that all of the bits in a multi-bit set must be set
+ * for this method to return {@code true}.</p>
+ *
+ * @param holder the int data containing the bits we're
+ * interested in
+ * @return {@code true} if all of the bits are set,
+ * else {@code false}
+ */
+ public boolean isAllSet(final int holder) {
+ return (holder & mask) == mask;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Sets the bits.
+ *
+ * @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}
+ */
+ public int set(final int holder) {
+ return holder | mask;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Sets the bits.
+ *
+ * @param holder the byte data containing the bits we're
+ * interested in
+ *
+ * @return the value of holder with the specified bits set
+ * to {@code 1}
+ */
+ public byte setByte(final byte holder) {
+ return (byte) set(holder);
+ }
+
+ /**
+ * Sets a boolean BitField.
+ *
+ * @param holder the int 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
+ */
+ public int setBoolean(final int holder, final boolean flag) {
+ return flag ? set(holder) : clear(holder);
+ }
+
+ /**
+ * Sets a boolean BitField.
+ *
+ * @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
+ */
+ public short setShortBoolean(final short holder, final boolean flag) {
+ return flag ? setShort(holder) : clearShort(holder);
+ }
+
+ /**
+ * Sets a boolean BitField.
+ *
+ * @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
+ */
+ public byte setByteBoolean(final byte holder, final boolean flag) {
+ return flag ? setByte(holder) : clearByte(holder);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/BooleanUtils.java b/src/main/java/org/apache/commons/lang3/BooleanUtils.java
new file mode 100644
index 000000000..ab73a42ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/BooleanUtils.java
@@ -0,0 +1,1219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * Operations on boolean primitives and Boolean objects.
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 2.0
+ */
+public class BooleanUtils {
+
+ private static final List<Boolean> BOOLEAN_LIST = Collections.unmodifiableList(Arrays.asList(Boolean.FALSE, Boolean.TRUE));
+
+ /**
+ * The false String {@code "false"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String FALSE = "false";
+
+ /**
+ * The no String {@code "no"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String NO = "no";
+
+ /**
+ * The off String {@code "off"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String OFF = "off";
+
+ /**
+ * The on String {@code "on"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String ON = "on";
+
+ /**
+ * The true String {@code "true"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String TRUE = "true";
+
+ /**
+ * The yes String {@code "yes"}.
+ *
+ * @since 3.12.0
+ */
+ public static final String YES = "yes";
+
+ /**
+ * Performs an 'and' operation on a set of booleans.
+ *
+ * <pre>
+ * BooleanUtils.and(true, true) = true
+ * BooleanUtils.and(false, false) = false
+ * BooleanUtils.and(true, false) = false
+ * BooleanUtils.and(true, true, false) = false
+ * BooleanUtils.and(true, true, true) = true
+ * </pre>
+ *
+ * @param array an array of {@code boolean}s
+ * @return the result of the logical 'and' operation. That is {@code false}
+ * if any of the parameters is {@code false} and {@code true} otherwise.
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ * @since 3.0.1
+ */
+ public static boolean and(final boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ for (final boolean element : array) {
+ if (!element) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Performs an 'and' operation on an array of Booleans.
+ * <pre>
+ * BooleanUtils.and(Boolean.TRUE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.and(Boolean.FALSE, Boolean.FALSE) = Boolean.FALSE
+ * BooleanUtils.and(Boolean.TRUE, Boolean.FALSE) = Boolean.FALSE
+ * BooleanUtils.and(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.and(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE) = Boolean.FALSE
+ * BooleanUtils.and(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE) = Boolean.FALSE
+ * BooleanUtils.and(null, null) = Boolean.FALSE
+ * </pre>
+ * <p>
+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false.
+ * </p>
+ *
+ * @param array an array of {@link Boolean}s
+ * @return the result of the logical 'and' operation. That is {@code false}
+ * if any of the parameters is {@code false} and {@code true} otherwise.
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ * @since 3.0.1
+ */
+ public static Boolean and(final Boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ return and(ArrayUtils.toPrimitive(array)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * Returns a new array of possible values (like an enum would).
+ * @return a new array of possible values (like an enum would).
+ * @since 3.12.0
+ */
+ public static Boolean[] booleanValues() {
+ return new Boolean[] {Boolean.FALSE, Boolean.TRUE};
+ }
+
+ /**
+ * Compares two {@code boolean} values. This is the same functionality as provided in Java 7.
+ *
+ * @param x the first {@code boolean} to compare
+ * @param y the second {@code boolean} 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 boolean x, final boolean y) {
+ if (x == y) {
+ return 0;
+ }
+ return x ? 1 : -1;
+ }
+
+ /**
+ * Performs the given action for each Boolean {@link BooleanUtils#values()}.
+ *
+ * @param action The action to be performed for each element
+ * @since 3.13.0
+ */
+ public static void forEach(final Consumer<Boolean> action) {
+ values().forEach(action);
+ }
+
+ /**
+ * Checks if a {@link Boolean} value is {@code false},
+ * handling {@code null} by returning {@code false}.
+ *
+ * <pre>
+ * BooleanUtils.isFalse(Boolean.TRUE) = false
+ * BooleanUtils.isFalse(Boolean.FALSE) = true
+ * BooleanUtils.isFalse(null) = false
+ * </pre>
+ *
+ * @param bool the boolean to check, null returns {@code false}
+ * @return {@code true} only if the input is non-{@code null} and {@code false}
+ * @since 2.1
+ */
+ public static boolean isFalse(final Boolean bool) {
+ return Boolean.FALSE.equals(bool);
+ }
+
+ /**
+ * Checks if a {@link Boolean} value is <i>not</i> {@code false},
+ * handling {@code null} by returning {@code true}.
+ *
+ * <pre>
+ * BooleanUtils.isNotFalse(Boolean.TRUE) = true
+ * BooleanUtils.isNotFalse(Boolean.FALSE) = false
+ * BooleanUtils.isNotFalse(null) = true
+ * </pre>
+ *
+ * @param bool the boolean to check, null returns {@code true}
+ * @return {@code true} if the input is {@code null} or {@code true}
+ * @since 2.3
+ */
+ public static boolean isNotFalse(final Boolean bool) {
+ return !isFalse(bool);
+ }
+
+ /**
+ * Checks if a {@link Boolean} value is <i>not</i> {@code true},
+ * handling {@code null} by returning {@code true}.
+ *
+ * <pre>
+ * BooleanUtils.isNotTrue(Boolean.TRUE) = false
+ * BooleanUtils.isNotTrue(Boolean.FALSE) = true
+ * BooleanUtils.isNotTrue(null) = true
+ * </pre>
+ *
+ * @param bool the boolean to check, null returns {@code true}
+ * @return {@code true} if the input is null or false
+ * @since 2.3
+ */
+ public static boolean isNotTrue(final Boolean bool) {
+ return !isTrue(bool);
+ }
+
+ /**
+ * Checks if a {@link Boolean} value is {@code true},
+ * handling {@code null} by returning {@code false}.
+ *
+ * <pre>
+ * BooleanUtils.isTrue(Boolean.TRUE) = true
+ * BooleanUtils.isTrue(Boolean.FALSE) = false
+ * BooleanUtils.isTrue(null) = false
+ * </pre>
+ *
+ * @param bool the boolean to check, {@code null} returns {@code false}
+ * @return {@code true} only if the input is non-null and true
+ * @since 2.1
+ */
+ public static boolean isTrue(final Boolean bool) {
+ return Boolean.TRUE.equals(bool);
+ }
+ /**
+ * Negates the specified boolean.
+ *
+ * <p>If {@code null} is passed in, {@code null} will be returned.</p>
+ *
+ * <p>NOTE: This returns {@code null} and will throw a {@link NullPointerException}
+ * if unboxed to a boolean.</p>
+ *
+ * <pre>
+ * BooleanUtils.negate(Boolean.TRUE) = Boolean.FALSE;
+ * BooleanUtils.negate(Boolean.FALSE) = Boolean.TRUE;
+ * BooleanUtils.negate(null) = null;
+ * </pre>
+ *
+ * @param bool the Boolean to negate, may be null
+ * @return the negated Boolean, or {@code null} if {@code null} input
+ */
+ public static Boolean negate(final Boolean bool) {
+ if (bool == null) {
+ return null;
+ }
+ return bool.booleanValue() ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ /**
+ * Performs a one-hot on an array of booleans.
+ * <p>
+ * This implementation returns true if one, and only one, of the supplied values is true.
+ * </p>
+ * <p>
+ * See also <a href="https://en.wikipedia.org/wiki/One-hot">One-hot</a>.
+ * </p>
+ * @param array an array of {@code boolean}s
+ * @return the result of the one-hot operations
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ */
+ public static boolean oneHot(final boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ boolean result = false;
+ for (final boolean element: array) {
+ if (element) {
+ if (result) {
+ return false;
+ }
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Performs a one-hot on an array of booleans.
+ * <p>
+ * This implementation returns true if one, and only one, of the supplied values is true.
+ * </p>
+ * <p>
+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false.
+ * </p>
+ * <p>
+ * See also <a href="https://en.wikipedia.org/wiki/One-hot">One-hot</a>.
+ * </p>
+ *
+ * @param array an array of {@code boolean}s
+ * @return the result of the one-hot operations
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ */
+ public static Boolean oneHot(final Boolean... array) {
+ return Boolean.valueOf(oneHot(ArrayUtils.toPrimitive(array)));
+ }
+
+ /**
+ * Performs an 'or' operation on a set of booleans.
+ *
+ * <pre>
+ * BooleanUtils.or(true, true) = true
+ * BooleanUtils.or(false, false) = false
+ * BooleanUtils.or(true, false) = true
+ * BooleanUtils.or(true, true, false) = true
+ * BooleanUtils.or(true, true, true) = true
+ * BooleanUtils.or(false, false, false) = false
+ * </pre>
+ *
+ * @param array an array of {@code boolean}s
+ * @return {@code true} if any of the arguments is {@code true}, and it returns {@code false} otherwise.
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ * @since 3.0.1
+ */
+ public static boolean or(final boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ for (final boolean element : array) {
+ if (element) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Performs an 'or' operation on an array of Booleans.
+ * <pre>
+ * BooleanUtils.or(Boolean.TRUE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.FALSE, Boolean.FALSE) = Boolean.FALSE
+ * BooleanUtils.or(Boolean.TRUE, Boolean.FALSE) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE) = Boolean.FALSE
+ * BooleanUtils.or(Boolean.TRUE, null) = Boolean.TRUE
+ * BooleanUtils.or(Boolean.FALSE, null) = Boolean.FALSE
+ * </pre>
+ * <p>
+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false.
+ * </p>
+ *
+ * @param array an array of {@link Boolean}s
+ * @return {@code true} if any of the arguments is {@code true}, and it returns {@code false} otherwise.
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ * @since 3.0.1
+ */
+ public static Boolean or(final Boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ return or(ArrayUtils.toPrimitive(array)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * Returns a new array of possible values (like an enum would).
+ * @return a new array of possible values (like an enum would).
+ * @since 3.12.0
+ */
+ public static boolean[] primitiveValues() {
+ return new boolean[] {false, true};
+ }
+
+ /**
+ * Converts a Boolean to a boolean handling {@code null}
+ * by returning {@code false}.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(Boolean.TRUE) = true
+ * BooleanUtils.toBoolean(Boolean.FALSE) = false
+ * BooleanUtils.toBoolean(null) = false
+ * </pre>
+ *
+ * @param bool the boolean to convert
+ * @return {@code true} or {@code false}, {@code null} returns {@code false}
+ */
+ public static boolean toBoolean(final Boolean bool) {
+ return bool != null && bool.booleanValue();
+ }
+
+ /**
+ * Converts an int to a boolean using the convention that {@code zero}
+ * is {@code false}, everything else is {@code true}.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(0) = false
+ * BooleanUtils.toBoolean(1) = true
+ * BooleanUtils.toBoolean(2) = true
+ * </pre>
+ *
+ * @param value the int to convert
+ * @return {@code true} if non-zero, {@code false}
+ * if zero
+ */
+ public static boolean toBoolean(final int value) {
+ return value != 0;
+ }
+
+ /**
+ * Converts an int to a boolean specifying the conversion values.
+ *
+ * <p>If the {@code trueValue} and {@code falseValue} are the same number then
+ * the return value will be {@code true} in case {@code value} matches it.</p>
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(0, 1, 0) = false
+ * BooleanUtils.toBoolean(1, 1, 0) = true
+ * BooleanUtils.toBoolean(1, 1, 1) = true
+ * BooleanUtils.toBoolean(2, 1, 2) = false
+ * BooleanUtils.toBoolean(2, 2, 0) = true
+ * </pre>
+ *
+ * @param value the {@link Integer} to convert
+ * @param trueValue the value to match for {@code true}
+ * @param falseValue the value to match for {@code false}
+ * @return {@code true} or {@code false}
+ * @throws IllegalArgumentException if {@code value} does not match neither
+ * {@code trueValue} no {@code falseValue}
+ */
+ public static boolean toBoolean(final int value, final int trueValue, final int falseValue) {
+ if (value == trueValue) {
+ return true;
+ }
+ if (value == falseValue) {
+ return false;
+ }
+ throw new IllegalArgumentException("The Integer did not match either specified value");
+ }
+
+ /**
+ * Converts an Integer to a boolean specifying the conversion values.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(Integer.valueOf(0), Integer.valueOf(1), Integer.valueOf(0)) = false
+ * BooleanUtils.toBoolean(Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(0)) = true
+ * BooleanUtils.toBoolean(Integer.valueOf(2), Integer.valueOf(1), Integer.valueOf(2)) = false
+ * BooleanUtils.toBoolean(Integer.valueOf(2), Integer.valueOf(2), Integer.valueOf(0)) = true
+ * BooleanUtils.toBoolean(null, null, Integer.valueOf(0)) = true
+ * </pre>
+ *
+ * @param value the Integer to convert
+ * @param trueValue the value to match for {@code true}, may be {@code null}
+ * @param falseValue the value to match for {@code false}, may be {@code null}
+ * @return {@code true} or {@code false}
+ * @throws IllegalArgumentException if no match
+ */
+ public static boolean toBoolean(final Integer value, final Integer trueValue, final Integer falseValue) {
+ if (value == null) {
+ if (trueValue == null) {
+ return true;
+ }
+ if (falseValue == null) {
+ return false;
+ }
+ } else if (value.equals(trueValue)) {
+ return true;
+ } else if (value.equals(falseValue)) {
+ return false;
+ }
+ throw new IllegalArgumentException("The Integer did not match either specified value");
+ }
+
+ /**
+ * Converts a String to a boolean (optimised for performance).
+ *
+ * <p>{@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'}
+ * (case insensitive) will return {@code true}. Otherwise,
+ * {@code false} is returned.</p>
+ *
+ * <p>This method performs 4 times faster (JDK1.4) than
+ * {@code Boolean.valueOf(String)}. However, this method accepts
+ * 'on' and 'yes', 't', 'y' as true values.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(null) = false
+ * BooleanUtils.toBoolean("true") = true
+ * BooleanUtils.toBoolean("TRUE") = true
+ * BooleanUtils.toBoolean("tRUe") = true
+ * BooleanUtils.toBoolean("on") = true
+ * BooleanUtils.toBoolean("yes") = true
+ * BooleanUtils.toBoolean("false") = false
+ * BooleanUtils.toBoolean("x gti") = false
+ * BooleanUtils.toBoolean("y") = true
+ * BooleanUtils.toBoolean("n") = false
+ * BooleanUtils.toBoolean("t") = true
+ * BooleanUtils.toBoolean("f") = false
+ * </pre>
+ *
+ * @param str the String to check
+ * @return the boolean value of the string, {@code false} if no match or the String is null
+ */
+ public static boolean toBoolean(final String str) {
+ return toBooleanObject(str) == Boolean.TRUE;
+ }
+
+ /**
+ * Converts a String to a Boolean throwing an exception if no match found.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean("true", "true", "false") = true
+ * BooleanUtils.toBoolean("false", "true", "false") = false
+ * </pre>
+ *
+ * @param str the String to check
+ * @param trueString the String to match for {@code true} (case-sensitive), may be {@code null}
+ * @param falseString the String to match for {@code false} (case-sensitive), may be {@code null}
+ * @return the boolean value of the string
+ * @throws IllegalArgumentException if the String doesn't match
+ */
+ public static boolean toBoolean(final String str, final String trueString, final String falseString) {
+ if (str == trueString) {
+ return true;
+ }
+ if (str == falseString) {
+ return false;
+ }
+ if (str != null) {
+ if (str.equals(trueString)) {
+ return true;
+ }
+ if (str.equals(falseString)) {
+ return false;
+ }
+ }
+ throw new IllegalArgumentException("The String did not match either specified value");
+ }
+
+ /**
+ * Converts a Boolean to a boolean handling {@code null}.
+ *
+ * <pre>
+ * BooleanUtils.toBooleanDefaultIfNull(Boolean.TRUE, false) = true
+ * BooleanUtils.toBooleanDefaultIfNull(Boolean.TRUE, true) = true
+ * BooleanUtils.toBooleanDefaultIfNull(Boolean.FALSE, true) = false
+ * BooleanUtils.toBooleanDefaultIfNull(Boolean.FALSE, false) = false
+ * BooleanUtils.toBooleanDefaultIfNull(null, true) = true
+ * BooleanUtils.toBooleanDefaultIfNull(null, false) = false
+ * </pre>
+ *
+ * @param bool the boolean object to convert to primitive
+ * @param valueIfNull the boolean value to return if the parameter {@code bool} is {@code null}
+ * @return {@code true} or {@code false}
+ */
+ public static boolean toBooleanDefaultIfNull(final Boolean bool, final boolean valueIfNull) {
+ if (bool == null) {
+ return valueIfNull;
+ }
+ return bool.booleanValue();
+ }
+
+ /**
+ * Converts an int to a Boolean using the convention that {@code zero}
+ * is {@code false}, everything else is {@code true}.
+ *
+ * <pre>
+ * BooleanUtils.toBoolean(0) = Boolean.FALSE
+ * BooleanUtils.toBoolean(1) = Boolean.TRUE
+ * BooleanUtils.toBoolean(2) = Boolean.TRUE
+ * </pre>
+ *
+ * @param value the int to convert
+ * @return Boolean.TRUE if non-zero, Boolean.FALSE if zero,
+ * {@code null} if {@code null}
+ */
+ public static Boolean toBooleanObject(final int value) {
+ return value == 0 ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ /**
+ * Converts an int to a Boolean specifying the conversion values.
+ *
+ * <p>NOTE: This method may return {@code null} and may throw a {@link NullPointerException}
+ * if unboxed to a {@code boolean}.</p>
+ *
+ * <p>The checks are done first for the {@code trueValue}, then for the {@code falseValue} and
+ * finally for the {@code nullValue}.</p>
+ *
+ * <pre>
+ * BooleanUtils.toBooleanObject(0, 0, 2, 3) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(0, 0, 0, 3) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(0, 0, 0, 0) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(2, 1, 2, 3) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(2, 1, 2, 2) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(3, 1, 2, 3) = null
+ * </pre>
+ *
+ * @param value the Integer to convert
+ * @param trueValue the value to match for {@code true}
+ * @param falseValue the value to match for {@code false}
+ * @param nullValue the value to match for {@code null}
+ * @return Boolean.TRUE, Boolean.FALSE, or {@code null}
+ * @throws IllegalArgumentException if no match
+ */
+ public static Boolean toBooleanObject(final int value, final int trueValue, final int falseValue, final int nullValue) {
+ if (value == trueValue) {
+ return Boolean.TRUE;
+ }
+ if (value == falseValue) {
+ return Boolean.FALSE;
+ }
+ if (value == nullValue) {
+ return null;
+ }
+ throw new IllegalArgumentException("The Integer did not match any specified value");
+ }
+
+ /**
+ * Converts an Integer to a Boolean using the convention that {@code zero}
+ * is {@code false}, every other numeric value is {@code true}.
+ *
+ * <p>{@code null} will be converted to {@code null}.</p>
+ *
+ * <p>NOTE: This method may return {@code null} and may throw a {@link NullPointerException}
+ * if unboxed to a {@code boolean}.</p>
+ *
+ * <pre>
+ * BooleanUtils.toBooleanObject(Integer.valueOf(0)) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(1)) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(null)) = null
+ * </pre>
+ *
+ * @param value the Integer to convert
+ * @return Boolean.TRUE if non-zero, Boolean.FALSE if zero,
+ * {@code null} if {@code null} input
+ */
+ public static Boolean toBooleanObject(final Integer value) {
+ if (value == null) {
+ return null;
+ }
+ return value.intValue() == 0 ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ /**
+ * Converts an Integer to a Boolean specifying the conversion values.
+ *
+ * <p>NOTE: This method may return {@code null} and may throw a {@link NullPointerException}
+ * if unboxed to a {@code boolean}.</p>
+ *
+ * <p>The checks are done first for the {@code trueValue}, then for the {@code falseValue} and
+ * finally for the {@code nullValue}.</p>
+ **
+ * <pre>
+ * BooleanUtils.toBooleanObject(Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(2), Integer.valueOf(3)) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(3)) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(2), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(2), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(2)) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(Integer.valueOf(3), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)) = null
+ * </pre>
+ *
+ * @param value the Integer to convert
+ * @param trueValue the value to match for {@code true}, may be {@code null}
+ * @param falseValue the value to match for {@code false}, may be {@code null}
+ * @param nullValue the value to match for {@code null}, may be {@code null}
+ * @return Boolean.TRUE, Boolean.FALSE, or {@code null}
+ * @throws IllegalArgumentException if no match
+ */
+ public static Boolean toBooleanObject(final Integer value, final Integer trueValue, final Integer falseValue, final Integer nullValue) {
+ if (value == null) {
+ if (trueValue == null) {
+ return Boolean.TRUE;
+ }
+ if (falseValue == null) {
+ return Boolean.FALSE;
+ }
+ if (nullValue == null) {
+ return null;
+ }
+ } else if (value.equals(trueValue)) {
+ return Boolean.TRUE;
+ } else if (value.equals(falseValue)) {
+ return Boolean.FALSE;
+ } else if (value.equals(nullValue)) {
+ return null;
+ }
+ throw new IllegalArgumentException("The Integer did not match any specified value");
+ }
+
+ /**
+ * Converts a String to a Boolean.
+ *
+ * <p>{@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'}, {@code 'yes'}
+ * or {@code '1'} (case insensitive) will return {@code true}.
+ * {@code 'false'}, {@code 'off'}, {@code 'n'}, {@code 'f'}, {@code 'no'}
+ * or {@code '0'} (case insensitive) will return {@code false}.
+ * Otherwise, {@code null} is returned.</p>
+ *
+ * <p>NOTE: This method may return {@code null} and may throw a {@link NullPointerException}
+ * if unboxed to a {@code boolean}.</p>
+ *
+ * <pre>
+ * // N.B. case is not significant
+ * BooleanUtils.toBooleanObject(null) = null
+ * BooleanUtils.toBooleanObject("true") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("T") = Boolean.TRUE // i.e. T[RUE]
+ * BooleanUtils.toBooleanObject("false") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("f") = Boolean.FALSE // i.e. f[alse]
+ * BooleanUtils.toBooleanObject("No") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("n") = Boolean.FALSE // i.e. n[o]
+ * BooleanUtils.toBooleanObject("on") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("ON") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("off") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("oFf") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("yes") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("Y") = Boolean.TRUE // i.e. Y[ES]
+ * BooleanUtils.toBooleanObject("1") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("0") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("blue") = null
+ * BooleanUtils.toBooleanObject("true ") = null // trailing space (too long)
+ * BooleanUtils.toBooleanObject("ono") = null // does not match on or no
+ * </pre>
+ *
+ * @param str the String to check; upper and lower case are treated as the same
+ * @return the Boolean value of the string, {@code null} if no match or {@code null} input
+ */
+ public static Boolean toBooleanObject(final String str) {
+ // Previously used equalsIgnoreCase, which was fast for interned 'true'.
+ // Non interned 'true' matched 15 times slower.
+ //
+ // Optimisation provides same performance as before for interned 'true'.
+ // Similar performance for null, 'false', and other strings not length 2/3/4.
+ // 'true'/'TRUE' match 4 times slower, 'tRUE'/'True' 7 times slower.
+ if (str == TRUE) {
+ return Boolean.TRUE;
+ }
+ if (str == null) {
+ return null;
+ }
+ switch (str.length()) {
+ case 1: {
+ final char ch0 = str.charAt(0);
+ if (ch0 == 'y' || ch0 == 'Y' ||
+ ch0 == 't' || ch0 == 'T' ||
+ ch0 == '1') {
+ return Boolean.TRUE;
+ }
+ if (ch0 == 'n' || ch0 == 'N' ||
+ ch0 == 'f' || ch0 == 'F' ||
+ ch0 == '0') {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 2: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ if ((ch0 == 'o' || ch0 == 'O') &&
+ (ch1 == 'n' || ch1 == 'N') ) {
+ return Boolean.TRUE;
+ }
+ if ((ch0 == 'n' || ch0 == 'N') &&
+ (ch1 == 'o' || ch1 == 'O') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 3: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ if ((ch0 == 'y' || ch0 == 'Y') &&
+ (ch1 == 'e' || ch1 == 'E') &&
+ (ch2 == 's' || ch2 == 'S') ) {
+ return Boolean.TRUE;
+ }
+ if ((ch0 == 'o' || ch0 == 'O') &&
+ (ch1 == 'f' || ch1 == 'F') &&
+ (ch2 == 'f' || ch2 == 'F') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 4: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ final char ch3 = str.charAt(3);
+ if ((ch0 == 't' || ch0 == 'T') &&
+ (ch1 == 'r' || ch1 == 'R') &&
+ (ch2 == 'u' || ch2 == 'U') &&
+ (ch3 == 'e' || ch3 == 'E') ) {
+ return Boolean.TRUE;
+ }
+ break;
+ }
+ case 5: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ final char ch3 = str.charAt(3);
+ final char ch4 = str.charAt(4);
+ if ((ch0 == 'f' || ch0 == 'F') &&
+ (ch1 == 'a' || ch1 == 'A') &&
+ (ch2 == 'l' || ch2 == 'L') &&
+ (ch3 == 's' || ch3 == 'S') &&
+ (ch4 == 'e' || ch4 == 'E') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * Converts a String to a Boolean throwing an exception if no match.
+ *
+ * <p>NOTE: This method may return {@code null} and may throw a {@link NullPointerException}
+ * if unboxed to a {@code boolean}.</p>
+ *
+ * <pre>
+ * BooleanUtils.toBooleanObject("true", "true", "false", "null") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(null, null, "false", "null") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(null, null, null, "null") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject(null, null, null, null) = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("false", "true", "false", "null") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("false", "true", "false", "false") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(null, "true", null, "false") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject(null, "true", null, null) = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("null", "true", "false", "null") = null
+ * </pre>
+ *
+ * @param str the String to check
+ * @param trueString the String to match for {@code true} (case-sensitive), may be {@code null}
+ * @param falseString the String to match for {@code false} (case-sensitive), may be {@code null}
+ * @param nullString the String to match for {@code null} (case-sensitive), may be {@code null}
+ * @return the Boolean value of the string, {@code null} if either the String matches {@code nullString}
+ * or if {@code null} input and {@code nullString} is {@code null}
+ * @throws IllegalArgumentException if the String doesn't match
+ */
+ public static Boolean toBooleanObject(final String str, final String trueString, final String falseString, final String nullString) {
+ if (str == null) {
+ if (trueString == null) {
+ return Boolean.TRUE;
+ }
+ if (falseString == null) {
+ return Boolean.FALSE;
+ }
+ if (nullString == null) {
+ return null;
+ }
+ } else if (str.equals(trueString)) {
+ return Boolean.TRUE;
+ } else if (str.equals(falseString)) {
+ return Boolean.FALSE;
+ } else if (str.equals(nullString)) {
+ return null;
+ }
+ // no match
+ throw new IllegalArgumentException("The String did not match any specified value");
+ }
+
+ /**
+ * Converts a boolean to an int using the convention that
+ * {@code true} is {@code 1} and {@code false} is {@code 0}.
+ *
+ * <pre>
+ * BooleanUtils.toInteger(true) = 1
+ * BooleanUtils.toInteger(false) = 0
+ * </pre>
+ *
+ * @param bool the boolean to convert
+ * @return one if {@code true}, zero if {@code false}
+ */
+ public static int toInteger(final boolean bool) {
+ return bool ? 1 : 0;
+ }
+
+ /**
+ * Converts a boolean to an int specifying the conversion values.
+ *
+ * <pre>
+ * BooleanUtils.toInteger(true, 1, 0) = 1
+ * BooleanUtils.toInteger(false, 1, 0) = 0
+ * </pre>
+ *
+ * @param bool the to convert
+ * @param trueValue the value to return if {@code true}
+ * @param falseValue the value to return if {@code false}
+ * @return the appropriate value
+ */
+ public static int toInteger(final boolean bool, final int trueValue, final int falseValue) {
+ return bool ? trueValue : falseValue;
+ }
+
+ /**
+ * Converts a Boolean to an int specifying the conversion values.
+ *
+ * <pre>
+ * BooleanUtils.toInteger(Boolean.TRUE, 1, 0, 2) = 1
+ * BooleanUtils.toInteger(Boolean.FALSE, 1, 0, 2) = 0
+ * BooleanUtils.toInteger(null, 1, 0, 2) = 2
+ * </pre>
+ *
+ * @param bool the Boolean to convert
+ * @param trueValue the value to return if {@code true}
+ * @param falseValue the value to return if {@code false}
+ * @param nullValue the value to return if {@code null}
+ * @return the appropriate value
+ */
+ public static int toInteger(final Boolean bool, final int trueValue, final int falseValue, final int nullValue) {
+ if (bool == null) {
+ return nullValue;
+ }
+ return bool.booleanValue() ? trueValue : falseValue;
+ }
+
+ /**
+ * Converts a boolean to an Integer using the convention that
+ * {@code true} is {@code 1} and {@code false} is {@code 0}.
+ *
+ * <pre>
+ * BooleanUtils.toIntegerObject(true) = Integer.valueOf(1)
+ * BooleanUtils.toIntegerObject(false) = Integer.valueOf(0)
+ * </pre>
+ *
+ * @param bool the boolean to convert
+ * @return one if {@code true}, zero if {@code false}
+ */
+ public static Integer toIntegerObject(final boolean bool) {
+ return bool ? NumberUtils.INTEGER_ONE : NumberUtils.INTEGER_ZERO;
+ }
+
+ /**
+ * Converts a boolean to an Integer specifying the conversion values.
+ *
+ * <pre>
+ * BooleanUtils.toIntegerObject(true, Integer.valueOf(1), Integer.valueOf(0)) = Integer.valueOf(1)
+ * BooleanUtils.toIntegerObject(false, Integer.valueOf(1), Integer.valueOf(0)) = Integer.valueOf(0)
+ * </pre>
+ *
+ * @param bool the to convert
+ * @param trueValue the value to return if {@code true}, may be {@code null}
+ * @param falseValue the value to return if {@code false}, may be {@code null}
+ * @return the appropriate value
+ */
+ public static Integer toIntegerObject(final boolean bool, final Integer trueValue, final Integer falseValue) {
+ return bool ? trueValue : falseValue;
+ }
+
+ /**
+ * Converts a Boolean to a Integer using the convention that
+ * {@code zero} is {@code false}.
+ *
+ * <p>{@code null} will be converted to {@code null}.</p>
+ *
+ * <pre>
+ * BooleanUtils.toIntegerObject(Boolean.TRUE) = Integer.valueOf(1)
+ * BooleanUtils.toIntegerObject(Boolean.FALSE) = Integer.valueOf(0)
+ * </pre>
+ *
+ * @param bool the Boolean to convert
+ * @return one if Boolean.TRUE, zero if Boolean.FALSE, {@code null} if {@code null}
+ */
+ public static Integer toIntegerObject(final Boolean bool) {
+ if (bool == null) {
+ return null;
+ }
+ return bool.booleanValue() ? NumberUtils.INTEGER_ONE : NumberUtils.INTEGER_ZERO;
+ }
+
+ /**
+ * Converts a Boolean to an Integer specifying the conversion values.
+ *
+ * <pre>
+ * BooleanUtils.toIntegerObject(Boolean.TRUE, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2)) = Integer.valueOf(1)
+ * BooleanUtils.toIntegerObject(Boolean.FALSE, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2)) = Integer.valueOf(0)
+ * BooleanUtils.toIntegerObject(null, Integer.valueOf(1), Integer.valueOf(0), Integer.valueOf(2)) = Integer.valueOf(2)
+ * </pre>
+ *
+ * @param bool the Boolean to convert
+ * @param trueValue the value to return if {@code true}, may be {@code null}
+ * @param falseValue the value to return if {@code false}, may be {@code null}
+ * @param nullValue the value to return if {@code null}, may be {@code null}
+ * @return the appropriate value
+ */
+ public static Integer toIntegerObject(final Boolean bool, final Integer trueValue, final Integer falseValue, final Integer nullValue) {
+ if (bool == null) {
+ return nullValue;
+ }
+ return bool.booleanValue() ? trueValue : falseValue;
+ }
+
+ /**
+ * Converts a boolean to a String returning one of the input Strings.
+ *
+ * <pre>
+ * BooleanUtils.toString(true, "true", "false") = "true"
+ * BooleanUtils.toString(false, "true", "false") = "false"
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @param trueString the String to return if {@code true}, may be {@code null}
+ * @param falseString the String to return if {@code false}, may be {@code null}
+ * @return one of the two input Strings
+ */
+ public static String toString(final boolean bool, final String trueString, final String falseString) {
+ return bool ? trueString : falseString;
+ }
+
+ /**
+ * Converts a Boolean to a String returning one of the input Strings.
+ *
+ * <pre>
+ * BooleanUtils.toString(Boolean.TRUE, "true", "false", null) = "true"
+ * BooleanUtils.toString(Boolean.FALSE, "true", "false", null) = "false"
+ * BooleanUtils.toString(null, "true", "false", null) = null;
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @param trueString the String to return if {@code true}, may be {@code null}
+ * @param falseString the String to return if {@code false}, may be {@code null}
+ * @param nullString the String to return if {@code null}, may be {@code null}
+ * @return one of the three input Strings
+ */
+ public static String toString(final Boolean bool, final String trueString, final String falseString, final String nullString) {
+ if (bool == null) {
+ return nullString;
+ }
+ return bool.booleanValue() ? trueString : falseString;
+ }
+
+ /**
+ * Converts a boolean to a String returning {@code 'on'}
+ * or {@code 'off'}.
+ *
+ * <pre>
+ * BooleanUtils.toStringOnOff(true) = "on"
+ * BooleanUtils.toStringOnOff(false) = "off"
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'on'}, {@code 'off'}, or {@code null}
+ */
+ public static String toStringOnOff(final boolean bool) {
+ return toString(bool, ON, OFF);
+ }
+
+ /**
+ * Converts a Boolean to a String returning {@code 'on'},
+ * {@code 'off'}, or {@code null}.
+ *
+ * <pre>
+ * BooleanUtils.toStringOnOff(Boolean.TRUE) = "on"
+ * BooleanUtils.toStringOnOff(Boolean.FALSE) = "off"
+ * BooleanUtils.toStringOnOff(null) = null;
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'on'}, {@code 'off'}, or {@code null}
+ */
+ public static String toStringOnOff(final Boolean bool) {
+ return toString(bool, ON, OFF, null);
+ }
+
+ /**
+ * Converts a boolean to a String returning {@code 'true'}
+ * or {@code 'false'}.
+ *
+ * <pre>
+ * BooleanUtils.toStringTrueFalse(true) = "true"
+ * BooleanUtils.toStringTrueFalse(false) = "false"
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'true'}, {@code 'false'}, or {@code null}
+ */
+ public static String toStringTrueFalse(final boolean bool) {
+ return toString(bool, TRUE, FALSE);
+ }
+
+ /**
+ * Converts a Boolean to a String returning {@code 'true'},
+ * {@code 'false'}, or {@code null}.
+ *
+ * <pre>
+ * BooleanUtils.toStringTrueFalse(Boolean.TRUE) = "true"
+ * BooleanUtils.toStringTrueFalse(Boolean.FALSE) = "false"
+ * BooleanUtils.toStringTrueFalse(null) = null;
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'true'}, {@code 'false'}, or {@code null}
+ */
+ public static String toStringTrueFalse(final Boolean bool) {
+ return toString(bool, TRUE, FALSE, null);
+ }
+
+ /**
+ * Converts a boolean to a String returning {@code 'yes'}
+ * or {@code 'no'}.
+ *
+ * <pre>
+ * BooleanUtils.toStringYesNo(true) = "yes"
+ * BooleanUtils.toStringYesNo(false) = "no"
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'yes'}, {@code 'no'}, or {@code null}
+ */
+ public static String toStringYesNo(final boolean bool) {
+ return toString(bool, YES, NO);
+ }
+
+ /**
+ * Converts a Boolean to a String returning {@code 'yes'},
+ * {@code 'no'}, or {@code null}.
+ *
+ * <pre>
+ * BooleanUtils.toStringYesNo(Boolean.TRUE) = "yes"
+ * BooleanUtils.toStringYesNo(Boolean.FALSE) = "no"
+ * BooleanUtils.toStringYesNo(null) = null;
+ * </pre>
+ *
+ * @param bool the Boolean to check
+ * @return {@code 'yes'}, {@code 'no'}, or {@code null}
+ */
+ public static String toStringYesNo(final Boolean bool) {
+ return toString(bool, YES, NO, null);
+ }
+
+ /**
+ * Returns an unmodifiable list of Booleans {@code [false, true]}.
+ *
+ * @return an unmodifiable list of Booleans {@code [false, true]}.
+ * @since 3.13.0
+ */
+ public static List<Boolean> values() {
+ return BOOLEAN_LIST;
+ }
+
+ /**
+ * Performs an xor on a set of booleans.
+ * <p>
+ * This behaves like an XOR gate;
+ * it returns true if the number of true values is odd,
+ * and false if the number of true values is zero or even.
+ * </p>
+ *
+ * <pre>
+ * BooleanUtils.xor(true, true) = false
+ * BooleanUtils.xor(false, false) = false
+ * BooleanUtils.xor(true, false) = true
+ * BooleanUtils.xor(true, false, false) = true
+ * BooleanUtils.xor(true, true, true) = true
+ * BooleanUtils.xor(true, true, true, true) = false
+ * </pre>
+ *
+ * @param array an array of {@code boolean}s
+ * @return true if the number of true values in the array is odd; otherwise returns false.
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ */
+ public static boolean xor(final boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ // false if the neutral element of the xor operator
+ boolean result = false;
+ for (final boolean element : array) {
+ result ^= element;
+ }
+
+ return result;
+ }
+
+ /**
+ * Performs an xor on an array of Booleans.
+ * <pre>
+ * BooleanUtils.xor(Boolean.TRUE, Boolean.TRUE) = Boolean.FALSE
+ * BooleanUtils.xor(Boolean.FALSE, Boolean.FALSE) = Boolean.FALSE
+ * BooleanUtils.xor(Boolean.TRUE, Boolean.FALSE) = Boolean.TRUE
+ * BooleanUtils.xor(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE) = Boolean.TRUE
+ * BooleanUtils.xor(Boolean.FALSE, null) = Boolean.FALSE
+ * BooleanUtils.xor(Boolean.TRUE, null) = Boolean.TRUE
+ * </pre>
+ * <p>
+ * Null array elements map to false, like {@code Boolean.parseBoolean(null)} and its callers return false.
+ * </p>
+ *
+ * @param array an array of {@link Boolean}s
+ * @return the result of the xor operations
+ * @throws NullPointerException if {@code array} is {@code null}
+ * @throws IllegalArgumentException if {@code array} is empty.
+ */
+ public static Boolean xor(final Boolean... array) {
+ ObjectUtils.requireNonEmpty(array, "array");
+ return xor(ArrayUtils.toPrimitive(array)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * {@link BooleanUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code BooleanUtils.negate(true);}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public BooleanUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/CharEncoding.java b/src/main/java/org/apache/commons/lang3/CharEncoding.java
new file mode 100644
index 000000000..621b44d8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharEncoding.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+
+/**
+ * Character encoding names required of every implementation of the Java platform.
+ *
+ * <p>According to <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html">JRE character
+ * encoding names</a>:</p>
+ *
+ * <p><cite>Every implementation of the Java platform is required to support the following character encodings.
+ * Consult the release documentation for your implementation to see if any other encodings are supported.
+ * </cite></p>
+ *
+ * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html">JRE character encoding names</a>
+ * @since 2.1
+ * @deprecated Java 7 introduced {@link java.nio.charset.StandardCharsets}, which defines these constants as
+ * {@link Charset} objects. Use {@link Charset#name()} to get the string values provided in this class.
+ * This class will be removed in a future release.
+ */
+@Deprecated
+public class CharEncoding {
+
+ /**
+ * ISO Latin Alphabet #1, also known as ISO-LATIN-1.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String ISO_8859_1 = "ISO-8859-1";
+
+ /**
+ * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block
+ * of the Unicode character set.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String US_ASCII = "US-ASCII";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial
+ * byte-order mark (either order accepted on input, big-endian used on output).
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String UTF_16 = "UTF-16";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String UTF_16BE = "UTF-16BE";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String UTF_16LE = "UTF-16LE";
+
+ /**
+ * Eight-bit Unicode Transformation Format.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.</p>
+ */
+ public static final String UTF_8 = "UTF-8";
+
+ /**
+ * Returns whether the named charset is supported.
+ *
+ * <p>This is similar to <a
+ * href="https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#isSupported%28java.lang.String%29">
+ * java.nio.charset.Charset.isSupported(String)</a> but handles more formats</p>
+ *
+ * @param name the name of the requested charset; may be either a canonical name or an alias, null returns false
+ * @return {@code true} if the charset is available in the current Java virtual machine
+ * @deprecated Please use {@link Charset#isSupported(String)} instead, although be aware that {@code null}
+ * values are not accepted by that method and an {@link IllegalCharsetNameException} may be thrown.
+ */
+ @Deprecated
+ public static boolean isSupported(final String name) {
+ if (name == null) {
+ return false;
+ }
+ try {
+ return Charset.isSupported(name);
+ } catch (final IllegalCharsetNameException ex) {
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/CharRange.java b/src/main/java/org/apache/commons/lang3/CharRange.java
new file mode 100644
index 000000000..0437e2d6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharRange.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * A contiguous range of characters, optionally negated.
+ *
+ * <p>Instances are immutable.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ */
+// TODO: This is no longer public and will be removed later as CharSet is moved
+// to depend on Range.
+final class CharRange implements Iterable<Character>, Serializable {
+
+ /**
+ * Required for serialization support. Lang version 2.0.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 8270183163158333422L;
+
+ /** The first character, inclusive, in the range. */
+ private final char start;
+
+ /** The last character, inclusive, in the range. */
+ private final char end;
+
+ /** True if the range is everything except the characters specified. */
+ private final boolean negated;
+
+ /** 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.
+ *
+ * <p>A negated range includes everything except that defined by the
+ * start and end characters.</p>
+ *
+ * <p>If start and end are in the wrong order, they are reversed.
+ * Thus {@code a-e} is the same as {@code e-a}.</p>
+ *
+ * @param start first character, inclusive, in this range
+ * @param end last character, inclusive, in this range
+ * @param negated true to express everything except the range
+ */
+ private CharRange(char start, char end, final boolean negated) {
+ if (start > end) {
+ final char temp = start;
+ start = end;
+ end = temp;
+ }
+
+ this.start = start;
+ this.end = end;
+ 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.
+ *
+ * <p>A negated range includes everything except that defined by the
+ * single character.</p>
+ *
+ * @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.
+ *
+ * <p>If start and end are in the wrong order, they are reversed.
+ * Thus {@code a-e} is the same as {@code e-a}.</p>
+ *
+ * @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.
+ *
+ * <p>A negated range includes everything except that defined by the
+ * start and end characters.</p>
+ *
+ * <p>If start and end are in the wrong order, they are reversed.
+ * Thus {@code a-e} is the same as {@code e-a}.</p>
+ *
+ * @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.
+ *
+ * <p>A negated range includes everything except that defined by the
+ * start and end characters.</p>
+ *
+ * @return {@code true} if negated
+ */
+ public boolean isNegated() {
+ return negated;
+ }
+
+ // Contains
+ /**
+ * Is the character specified contained in this range.
+ *
+ * @param ch the character to check
+ * @return {@code true} if this range contains the input character
+ */
+ public boolean contains(final char ch) {
+ return (ch >= start && ch <= end) != negated;
+ }
+
+ /**
+ * Are all the characters of the passed in range contained in
+ * this range.
+ *
+ * @param range the range to check against
+ * @return {@code true} if this range entirely contains the input range
+ * @throws NullPointerException if {@code null} input
+ */
+ public boolean contains(final CharRange range) {
+ Objects.requireNonNull(range, "range");
+ if (negated) {
+ if (range.negated) {
+ return start >= range.start && end <= range.end;
+ }
+ return range.end < start || range.start > end;
+ }
+ if (range.negated) {
+ return start == 0 && end == Character.MAX_VALUE;
+ }
+ return start <= range.start && end >= range.end;
+ }
+
+ // Basics
+ /**
+ * Compares two CharRange objects, returning true if they represent
+ * exactly the same range of characters defined in the same way.
+ *
+ * @param obj the object to compare to
+ * @return true if equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof CharRange)) {
+ return false;
+ }
+ final CharRange other = (CharRange) obj;
+ return start == other.start && end == other.end && negated == other.negated;
+ }
+
+ /**
+ * Gets a hashCode compatible with the equals method.
+ *
+ * @return a suitable hashCode
+ */
+ @Override
+ public int hashCode() {
+ return 83 + start + 7 * end + (negated ? 1 : 0);
+ }
+
+ /**
+ * Gets a string representation of the character range.
+ *
+ * @return string representation of this range
+ */
+ @Override
+ public String toString() {
+ if (iToString == null) {
+ final StringBuilder buf = new StringBuilder(4);
+ if (isNegated()) {
+ buf.append('^');
+ }
+ buf.append(start);
+ if (start != end) {
+ buf.append('-');
+ buf.append(end);
+ }
+ iToString = buf.toString();
+ }
+ return iToString;
+ }
+
+ /**
+ * Returns an iterator which can be used to walk through the characters described by this range.
+ *
+ * <p>#NotThreadSafe# the iterator is not thread-safe</p>
+ * @return an iterator to the chars represented by this range
+ * @since 2.5
+ */
+ @Override
+ public Iterator<Character> iterator() {
+ return new CharacterIterator(this);
+ }
+
+ /**
+ * Character {@link Iterator}.
+ * <p>#NotThreadSafe#</p>
+ */
+ private static class CharacterIterator implements Iterator<Character> {
+ /** 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/CharSequenceUtils.java b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
new file mode 100644
index 000000000..f63b4248c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+/**
+ * Operations on {@link CharSequence} that are
+ * {@code null} safe.
+ *
+ * @see CharSequence
+ * @since 3.0
+ */
+public class CharSequenceUtils {
+
+ private static final int NOT_FOUND = -1;
+
+ static final int TO_STRING_LIMIT = 16;
+
+ private static boolean checkLaterThan1(final CharSequence cs, final CharSequence searchChar, final int len2, final int start1) {
+ for (int i = 1, j = len2 - 1; i <= j; i++, j--) {
+ if (cs.charAt(start1 + i) != searchChar.charAt(i) || cs.charAt(start1 + j) != searchChar.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
+ *
+ * @param cs the {@link CharSequence} to be processed
+ * @param searchChar the {@link CharSequence} to be searched for
+ * @param start the start index
+ * @return the index where the search sequence was found
+ */
+ static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
+ if (cs instanceof String) {
+ return ((String) cs).indexOf(searchChar.toString(), start);
+ }
+ if (cs instanceof StringBuilder) {
+ return ((StringBuilder) cs).indexOf(searchChar.toString(), start);
+ }
+ if (cs instanceof StringBuffer) {
+ return ((StringBuffer) cs).indexOf(searchChar.toString(), start);
+ }
+ return cs.toString().indexOf(searchChar.toString(), start);
+// if (cs instanceof String && searchChar instanceof String) {
+// // TODO: Do we assume searchChar is usually relatively small;
+// // If so then calling toString() on it is better than reverting to
+// // the green implementation in the else block
+// return ((String) cs).indexOf((String) searchChar, start);
+// } else {
+// // TODO: Implement rather than convert to String
+// return cs.toString().indexOf(searchChar.toString(), start);
+// }
+ }
+
+ /**
+ * Returns the index within {@code cs} of the first occurrence of the
+ * specified character, starting the search at the specified index.
+ * <p>
+ * If a character with value {@code searchChar} occurs in the
+ * character sequence represented by the {@code cs}
+ * object at an index no smaller than {@code start}, then
+ * the index of the first such occurrence is returned. For values
+ * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
+ * this is the smallest value <i>k</i> such that:
+ * </p>
+ * <blockquote><pre>
+ * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * smallest value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
+ * </pre></blockquote>
+ * <p>
+ * is true. In either case, if no such character occurs inm {@code cs}
+ * at or after position {@code start}, then
+ * {@code -1} is returned.
+ * </p>
+ * <p>
+ * There is no restriction on the value of {@code start}. If it
+ * is negative, it has the same effect as if it were zero: the entire
+ * {@link CharSequence} may be searched. If it is greater than
+ * the length of {@code cs}, it has the same effect as if it were
+ * equal to the length of {@code cs}: {@code -1} is returned.
+ * </p>
+ * <p>All indices are specified in {@code char} values
+ * (Unicode code units).
+ * </p>
+ *
+ * @param cs the {@link CharSequence} to be processed, not null
+ * @param searchChar the char to be searched for
+ * @param start the start index, negative starts at the string start
+ * @return the index where the search char was found, -1 if not found
+ * @since 3.6 updated to behave more like {@link String}
+ */
+ static int indexOf(final CharSequence cs, final int searchChar, int start) {
+ if (cs instanceof String) {
+ return ((String) cs).indexOf(searchChar, start);
+ }
+ final int sz = cs.length();
+ if (start < 0) {
+ start = 0;
+ }
+ if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+ for (int i = start; i < sz; i++) {
+ if (cs.charAt(i) == searchChar) {
+ return i;
+ }
+ }
+ return NOT_FOUND;
+ }
+ //supplementary characters (LANG1300)
+ if (searchChar <= Character.MAX_CODE_POINT) {
+ final char[] chars = Character.toChars(searchChar);
+ for (int i = start; i < sz - 1; i++) {
+ final char high = cs.charAt(i);
+ final char low = cs.charAt(i + 1);
+ if (high == chars[0] && low == chars[1]) {
+ return i;
+ }
+ }
+ }
+ return NOT_FOUND;
+ }
+
+ /**
+ * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
+ *
+ * @param cs the {@link CharSequence} to be processed
+ * @param searchChar the {@link CharSequence} to find
+ * @param start the start index
+ * @return the index where the search sequence was found
+ */
+ static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, int start) {
+ if (searchChar == null || cs == null) {
+ return NOT_FOUND;
+ }
+ if (searchChar instanceof String) {
+ if (cs instanceof String) {
+ return ((String) cs).lastIndexOf((String) searchChar, start);
+ }
+ if (cs instanceof StringBuilder) {
+ return ((StringBuilder) cs).lastIndexOf((String) searchChar, start);
+ }
+ if (cs instanceof StringBuffer) {
+ return ((StringBuffer) cs).lastIndexOf((String) searchChar, start);
+ }
+ }
+
+ final int len1 = cs.length();
+ final int len2 = searchChar.length();
+
+ if (start > len1) {
+ start = len1;
+ }
+
+ if (start < 0 || len2 > len1) {
+ return NOT_FOUND;
+ }
+
+ if (len2 == 0) {
+ return start;
+ }
+
+ if (len2 <= TO_STRING_LIMIT) {
+ if (cs instanceof String) {
+ return ((String) cs).lastIndexOf(searchChar.toString(), start);
+ }
+ if (cs instanceof StringBuilder) {
+ return ((StringBuilder) cs).lastIndexOf(searchChar.toString(), start);
+ }
+ if (cs instanceof StringBuffer) {
+ return ((StringBuffer) cs).lastIndexOf(searchChar.toString(), start);
+ }
+ }
+
+ if (start + len2 > len1) {
+ start = len1 - len2;
+ }
+
+ final char char0 = searchChar.charAt(0);
+
+ int i = start;
+ while (true) {
+ while (cs.charAt(i) != char0) {
+ i--;
+ if (i < 0) {
+ return NOT_FOUND;
+ }
+ }
+ if (checkLaterThan1(cs, searchChar, len2, i)) {
+ return i;
+ }
+ i--;
+ if (i < 0) {
+ return NOT_FOUND;
+ }
+ }
+ }
+
+ /**
+ * Returns the index within {@code cs} of the last occurrence of
+ * the specified character, searching backward starting at the
+ * specified index. For values of {@code searchChar} in the range
+ * from 0 to 0xFFFF (inclusive), the index returned is the largest
+ * value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= start)
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * largest value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= start)
+ * </pre></blockquote>
+ * is true. In either case, if no such character occurs in {@code cs}
+ * at or before position {@code start}, then {@code -1} is returned.
+ *
+ * <p>
+ * All indices are specified in {@code char} values
+ * (Unicode code units).
+ * </p>
+ *
+ * @param cs the {@link CharSequence} to be processed
+ * @param searchChar the char to be searched for
+ * @param start the start index, negative returns -1, beyond length starts at end
+ * @return the index where the search char was found, -1 if not found
+ * @since 3.6 updated to behave more like {@link String}
+ */
+ static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
+ if (cs instanceof String) {
+ return ((String) cs).lastIndexOf(searchChar, start);
+ }
+ final int sz = cs.length();
+ if (start < 0) {
+ return NOT_FOUND;
+ }
+ if (start >= sz) {
+ start = sz - 1;
+ }
+ if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+ for (int i = start; i >= 0; --i) {
+ if (cs.charAt(i) == searchChar) {
+ return i;
+ }
+ }
+ return NOT_FOUND;
+ }
+ //supplementary characters (LANG1300)
+ //NOTE - we must do a forward traversal for this to avoid duplicating code points
+ if (searchChar <= Character.MAX_CODE_POINT) {
+ final char[] chars = Character.toChars(searchChar);
+ //make sure it's not the last index
+ if (start == sz - 1) {
+ return NOT_FOUND;
+ }
+ for (int i = start; i >= 0; i--) {
+ final char high = cs.charAt(i);
+ final char low = cs.charAt(i + 1);
+ if (chars[0] == high && chars[1] == low) {
+ return i;
+ }
+ }
+ }
+ return NOT_FOUND;
+ }
+
+ /**
+ * Green implementation of regionMatches.
+ *
+ * @param cs the {@link CharSequence} to be processed
+ * @param ignoreCase whether or not to be case-insensitive
+ * @param thisStart the index to start on the {@code cs} CharSequence
+ * @param substring the {@link CharSequence} to be looked for
+ * @param start the index to start on the {@code substring} CharSequence
+ * @param length character length of the region
+ * @return whether the region matched
+ */
+ static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
+ final CharSequence substring, final int start, final int length) {
+ if (cs instanceof String && substring instanceof String) {
+ return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
+ }
+ int index1 = thisStart;
+ int index2 = start;
+ int tmpLen = length;
+
+ // Extract these first so we detect NPEs the same as the java.lang.String version
+ final int srcLen = cs.length() - thisStart;
+ final int otherLen = substring.length() - start;
+
+ // Check for invalid parameters
+ if (thisStart < 0 || start < 0 || length < 0) {
+ return false;
+ }
+
+ // Check that the regions are long enough
+ if (srcLen < length || otherLen < length) {
+ return false;
+ }
+
+ while (tmpLen-- > 0) {
+ final char c1 = cs.charAt(index1++);
+ final char c2 = substring.charAt(index2++);
+
+ if (c1 == c2) {
+ continue;
+ }
+
+ if (!ignoreCase) {
+ return false;
+ }
+
+ // The real same check as in String.regionMatches():
+ final char u1 = Character.toUpperCase(c1);
+ final char u2 = Character.toUpperCase(c2);
+ if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a new {@link CharSequence} that is a subsequence of this
+ * sequence starting with the {@code char} value at the specified index.
+ *
+ * <p>This provides the {@link CharSequence} equivalent to {@link String#substring(int)}.
+ * The length (in {@code char}) of the returned sequence is {@code length() - start},
+ * so if {@code start == end} then an empty sequence is returned.</p>
+ *
+ * @param cs the specified subsequence, null returns null
+ * @param start the start index, inclusive, valid
+ * @return a new subsequence, may be null
+ * @throws IndexOutOfBoundsException if {@code start} is negative or if
+ * {@code start} is greater than {@code length()}
+ */
+ public static CharSequence subSequence(final CharSequence cs, final int start) {
+ return cs == null ? null : cs.subSequence(start, cs.length());
+ }
+
+ /**
+ * Converts the given CharSequence to a char[].
+ *
+ * @param source the {@link CharSequence} to be processed.
+ * @return the resulting char array, never null.
+ * @since 3.11
+ */
+ public static char[] toCharArray(final CharSequence source) {
+ final int len = StringUtils.length(source);
+ if (len == 0) {
+ return ArrayUtils.EMPTY_CHAR_ARRAY;
+ }
+ if (source instanceof String) {
+ return ((String) source).toCharArray();
+ }
+ final char[] array = new char[len];
+ for (int i = 0; i < len; i++) {
+ array[i] = source.charAt(i);
+ }
+ return array;
+ }
+
+ /**
+ * {@link CharSequenceUtils} instances should NOT be constructed in
+ * standard programming.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public CharSequenceUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/CharSet.java b/src/main/java/org/apache/commons/lang3/CharSet.java
new file mode 100644
index 000000000..a6b7bcbb5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharSet.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * A set of characters.
+ *
+ * <p>Instances are immutable, but instances of subclasses may not be.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ */
+public class CharSet implements Serializable {
+
+ /**
+ * Required for serialization support. Lang version 2.0.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 5947847346149275958L;
+
+ /**
+ * A CharSet defining no characters.
+ * @since 2.0
+ */
+ public static final CharSet EMPTY = new CharSet((String) null);
+
+ /**
+ * A CharSet defining ASCII alphabetic characters "a-zA-Z".
+ * @since 2.0
+ */
+ public static final CharSet ASCII_ALPHA = new CharSet("a-zA-Z");
+
+ /**
+ * A CharSet defining ASCII alphabetic characters "a-z".
+ * @since 2.0
+ */
+ public static final CharSet ASCII_ALPHA_LOWER = new CharSet("a-z");
+
+ /**
+ * A CharSet defining ASCII alphabetic characters "A-Z".
+ * @since 2.0
+ */
+ public static final CharSet ASCII_ALPHA_UPPER = new CharSet("A-Z");
+
+ /**
+ * A CharSet defining ASCII alphabetic characters "0-9".
+ * @since 2.0
+ */
+ public static final CharSet ASCII_NUMERIC = new CharSet("0-9");
+
+ /**
+ * A Map of the common cases used in the factory.
+ * Subclasses can add more common patterns if desired
+ * @since 2.0
+ */
+ protected static final Map<String, CharSet> COMMON = Collections.synchronizedMap(new HashMap<>());
+
+ static {
+ COMMON.put(null, EMPTY);
+ COMMON.put(StringUtils.EMPTY, EMPTY);
+ COMMON.put("a-zA-Z", ASCII_ALPHA);
+ COMMON.put("A-Za-z", ASCII_ALPHA);
+ COMMON.put("a-z", ASCII_ALPHA_LOWER);
+ COMMON.put("A-Z", ASCII_ALPHA_UPPER);
+ COMMON.put("0-9", ASCII_NUMERIC);
+ }
+
+ /** The set of CharRange objects. */
+ private final Set<CharRange> set = Collections.synchronizedSet(new HashSet<>());
+
+ /**
+ * Factory method to create a new CharSet using a special syntax.
+ *
+ * <ul>
+ * <li>{@code null} or empty string ("")
+ * - set containing no characters</li>
+ * <li>Single character, such as "a"
+ * - set containing just that character</li>
+ * <li>Multi character, such as "a-e"
+ * - set containing characters from one character to the other</li>
+ * <li>Negated, such as "^a" or "^a-e"
+ * - set containing all characters except those defined</li>
+ * <li>Combinations, such as "abe-g"
+ * - set containing all the characters from the individual sets</li>
+ * </ul>
+ *
+ * <p>The matching order is:</p>
+ * <ol>
+ * <li>Negated multi character range, such as "^a-e"
+ * <li>Ordinary multi character range, such as "a-e"
+ * <li>Negated single character, such as "^a"
+ * <li>Ordinary single character, such as "a"
+ * </ol>
+ *
+ * <p>Matching works left to right. Once a match is found the
+ * search starts again from the next character.</p>
+ *
+ * <p>If the same range is defined twice using the same syntax, only
+ * one range will be kept.
+ * Thus, "a-ca-c" creates only one range of "a-c".</p>
+ *
+ * <p>If the start and end of a range are in the wrong order,
+ * they are reversed. Thus "a-e" is the same as "e-a".
+ * As a result, "a-ee-a" would create only one range,
+ * as the "a-e" and "e-a" are the same.</p>
+ *
+ * <p>The set of characters represented is the union of the specified ranges.</p>
+ *
+ * <p>There are two ways to add a literal negation character ({@code ^}):</p>
+ * <ul>
+ * <li>As the last character in a string, e.g. {@code CharSet.getInstance("a-z^")}</li>
+ * <li>As a separate element, e.g. {@code CharSet.getInstance("^", "a-z")}</li>
+ * </ul>
+ *
+ * <p>Examples using the negation character:</p>
+ * <pre>
+ * CharSet.getInstance("^a-c").contains('a') = false
+ * CharSet.getInstance("^a-c").contains('d') = true
+ * CharSet.getInstance("^^a-c").contains('a') = true // (only '^' is negated)
+ * CharSet.getInstance("^^a-c").contains('^') = false
+ * CharSet.getInstance("^a-cd-f").contains('d') = true
+ * CharSet.getInstance("a-c^").contains('^') = true
+ * CharSet.getInstance("^", "a-c").contains('^') = true
+ * </pre>
+ *
+ * <p>All CharSet objects returned by this method will be immutable.</p>
+ *
+ * @param setStrs Strings to merge into the set, may be null
+ * @return a CharSet instance
+ * @since 2.4
+ */
+ public static CharSet getInstance(final String... setStrs) {
+ if (setStrs == null) {
+ return null;
+ }
+ if (setStrs.length == 1) {
+ final CharSet common = COMMON.get(setStrs[0]);
+ if (common != null) {
+ return common;
+ }
+ }
+ return new CharSet(setStrs);
+ }
+
+ /**
+ * Constructs a new CharSet using the set syntax.
+ * Each string is merged in with the set.
+ *
+ * @param set Strings to merge into the initial set
+ * @throws NullPointerException if set is {@code null}
+ */
+ protected CharSet(final String... set) {
+ Stream.of(set).forEach(this::add);
+ }
+
+ /**
+ * Add a set definition string to the {@link CharSet}.
+ *
+ * @param str set definition string
+ */
+ protected void add(final String str) {
+ if (str == null) {
+ return;
+ }
+
+ final int len = str.length();
+ int pos = 0;
+ while (pos < len) {
+ final int remainder = len - pos;
+ if (remainder >= 4 && str.charAt(pos) == '^' && str.charAt(pos + 2) == '-') {
+ // negated range
+ set.add(CharRange.isNotIn(str.charAt(pos + 1), str.charAt(pos + 3)));
+ pos += 4;
+ } else if (remainder >= 3 && str.charAt(pos + 1) == '-') {
+ // range
+ set.add(CharRange.isIn(str.charAt(pos), str.charAt(pos + 2)));
+ pos += 3;
+ } else if (remainder >= 2 && str.charAt(pos) == '^') {
+ // negated char
+ set.add(CharRange.isNot(str.charAt(pos + 1)));
+ pos += 2;
+ } else {
+ // char
+ set.add(CharRange.is(str.charAt(pos)));
+ pos += 1;
+ }
+ }
+ }
+
+ /**
+ * 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}.
+ *
+ * @param ch the character to check for
+ * @return {@code true} if the set contains the characters
+ */
+ public boolean contains(final char ch) {
+ synchronized (set) {
+ return set.stream().anyMatch(range -> range.contains(ch));
+ }
+ }
+
+ // Basics
+ /**
+ * Compares two {@link CharSet} objects, returning true if they represent
+ * exactly the same set of characters defined in the same way.
+ *
+ * <p>The two sets {@code abc} and {@code a-c} are <i>not</i>
+ * equal according to this method.</p>
+ *
+ * @param obj the object to compare to
+ * @return true if equal
+ * @since 2.0
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof CharSet)) {
+ return false;
+ }
+ final CharSet other = (CharSet) obj;
+ return set.equals(other.set);
+ }
+
+ /**
+ * Gets a hash code compatible with the equals method.
+ *
+ * @return a suitable hash code
+ * @since 2.0
+ */
+ @Override
+ public int hashCode() {
+ return 89 + set.hashCode();
+ }
+
+ /**
+ * Gets a string representation of the set.
+ *
+ * @return string representation of the set
+ */
+ @Override
+ public String toString() {
+ return set.toString();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/CharSetUtils.java b/src/main/java/org/apache/commons/lang3/CharSetUtils.java
new file mode 100644
index 000000000..b1efea59d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharSetUtils.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * Operations on {@link CharSet} instances.
+ *
+ * <p>This class handles {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @see CharSet
+ * @since 1.0
+ */
+public class CharSetUtils {
+
+ /**
+ * Takes an argument in set-syntax, see evaluateSet,
+ * and identifies whether any of the characters are present in the specified string.
+ *
+ * <pre>
+ * CharSetUtils.containsAny(null, *) = false
+ * CharSetUtils.containsAny("", *) = false
+ * CharSetUtils.containsAny(*, null) = false
+ * CharSetUtils.containsAny(*, "") = false
+ * CharSetUtils.containsAny("hello", "k-p") = true
+ * CharSetUtils.containsAny("hello", "a-d") = false
+ * </pre>
+ *
+ * @see CharSet#getInstance(String...) for set-syntax.
+ * @param str String to look for characters in, may be null
+ * @param set String[] set of characters to identify, may be null
+ * @return whether or not the characters in the set are in the primary string
+ * @since 3.2
+ */
+ public static boolean containsAny(final String str, final String... set) {
+ if (StringUtils.isEmpty(str) || deepEmpty(set)) {
+ return false;
+ }
+ final CharSet chars = CharSet.getInstance(set);
+ for (final char c : str.toCharArray()) {
+ if (chars.contains(c)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Takes an argument in set-syntax, see evaluateSet,
+ * and returns the number of characters present in the specified string.
+ *
+ * <pre>
+ * CharSetUtils.count(null, *) = 0
+ * CharSetUtils.count("", *) = 0
+ * CharSetUtils.count(*, null) = 0
+ * CharSetUtils.count(*, "") = 0
+ * CharSetUtils.count("hello", "k-p") = 3
+ * CharSetUtils.count("hello", "a-e") = 1
+ * </pre>
+ *
+ * @see CharSet#getInstance(String...) for set-syntax.
+ * @param str String to count characters in, may be null
+ * @param set String[] set of characters to count, may be null
+ * @return the character count, zero if null string input
+ */
+ public static int count(final String str, final String... set) {
+ if (StringUtils.isEmpty(str) || deepEmpty(set)) {
+ return 0;
+ }
+ final CharSet chars = CharSet.getInstance(set);
+ int count = 0;
+ for (final char c : str.toCharArray()) {
+ if (chars.contains(c)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Determines whether or not all the Strings in an array are
+ * empty or not.
+ *
+ * @param strings String[] whose elements are being checked for emptiness
+ * @return whether or not the String is empty
+ */
+ private static boolean deepEmpty(final String[] strings) {
+ return Streams.of(strings).allMatch(StringUtils::isEmpty);
+ }
+
+ /**
+ * Takes an argument in set-syntax, see evaluateSet,
+ * and deletes any of characters present in the specified string.
+ *
+ * <pre>
+ * CharSetUtils.delete(null, *) = null
+ * CharSetUtils.delete("", *) = ""
+ * CharSetUtils.delete(*, null) = *
+ * CharSetUtils.delete(*, "") = *
+ * CharSetUtils.delete("hello", "hl") = "eo"
+ * CharSetUtils.delete("hello", "le") = "ho"
+ * </pre>
+ *
+ * @see CharSet#getInstance(String...) for set-syntax.
+ * @param str String to delete characters from, may be null
+ * @param set String[] set of characters to delete, may be null
+ * @return the modified String, {@code null} if null string input
+ */
+ public static String delete(final String str, final String... set) {
+ if (StringUtils.isEmpty(str) || deepEmpty(set)) {
+ return str;
+ }
+ return modify(str, set, false);
+ }
+
+ /**
+ * Takes an argument in set-syntax, see evaluateSet,
+ * and keeps any of characters present in the specified string.
+ *
+ * <pre>
+ * CharSetUtils.keep(null, *) = null
+ * CharSetUtils.keep("", *) = ""
+ * CharSetUtils.keep(*, null) = ""
+ * CharSetUtils.keep(*, "") = ""
+ * CharSetUtils.keep("hello", "hl") = "hll"
+ * CharSetUtils.keep("hello", "le") = "ell"
+ * </pre>
+ *
+ * @see CharSet#getInstance(String...) for set-syntax.
+ * @param str String to keep characters from, may be null
+ * @param set String[] set of characters to keep, may be null
+ * @return the modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String keep(final String str, final String... set) {
+ if (str == null) {
+ return null;
+ }
+ if (str.isEmpty() || deepEmpty(set)) {
+ return StringUtils.EMPTY;
+ }
+ return modify(str, set, true);
+ }
+
+ /**
+ * Implementation of delete and keep
+ *
+ * @param str String to modify characters within
+ * @param set String[] set of characters to modify
+ * @param expect whether to evaluate on match, or non-match
+ * @return the modified String, not null
+ */
+ private static String modify(final String str, final String[] set, final boolean expect) {
+ final CharSet chars = CharSet.getInstance(set);
+ final StringBuilder buffer = new StringBuilder(str.length());
+ final char[] chrs = str.toCharArray();
+ for (final char chr : chrs) {
+ if (chars.contains(chr) == expect) {
+ buffer.append(chr);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Squeezes any repetitions of a character that is mentioned in the
+ * supplied set.
+ *
+ * <pre>
+ * CharSetUtils.squeeze(null, *) = null
+ * CharSetUtils.squeeze("", *) = ""
+ * CharSetUtils.squeeze(*, null) = *
+ * CharSetUtils.squeeze(*, "") = *
+ * CharSetUtils.squeeze("hello", "k-p") = "helo"
+ * CharSetUtils.squeeze("hello", "a-e") = "hello"
+ * </pre>
+ *
+ * @see CharSet#getInstance(String...) for set-syntax.
+ * @param str the string to squeeze, may be null
+ * @param set the character set to use for manipulation, may be null
+ * @return the modified String, {@code null} if null string input
+ */
+ public static String squeeze(final String str, final String... set) {
+ if (StringUtils.isEmpty(str) || deepEmpty(set)) {
+ return str;
+ }
+ final CharSet chars = CharSet.getInstance(set);
+ final StringBuilder buffer = new StringBuilder(str.length());
+ final char[] chrs = str.toCharArray();
+ final int sz = chrs.length;
+ char lastChar = chrs[0];
+ char ch;
+ Character inChars = null;
+ Character notInChars = null;
+ buffer.append(lastChar);
+ for (int i = 1; i < sz; i++) {
+ ch = chrs[i];
+ if (ch == lastChar) {
+ if (inChars != null && ch == inChars) {
+ continue;
+ }
+ if (notInChars == null || ch != notInChars) {
+ if (chars.contains(ch)) {
+ inChars = ch;
+ continue;
+ }
+ notInChars = ch;
+ }
+ }
+ buffer.append(ch);
+ lastChar = ch;
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * CharSetUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code CharSetUtils.evaluateSet(null);}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public CharSetUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/CharUtils.java b/src/main/java/org/apache/commons/lang3/CharUtils.java
new file mode 100644
index 000000000..52b864d6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/CharUtils.java
@@ -0,0 +1,521 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Objects;
+
+/**
+ * Operations on char primitives and Character objects.
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 2.1
+ */
+public class CharUtils {
+
+ private static final String[] CHAR_STRING_ARRAY = new String[128];
+
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * Linefeed character LF ({@code '\n'}, Unicode 000a).
+ *
+ * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 2.2
+ */
+ public static final char LF = '\n';
+
+ /**
+ * Carriage return character CR ('\r', Unicode 000d).
+ *
+ * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 2.2
+ */
+ public static final char CR = '\r';
+
+ /**
+ * {@code \u0000} null control character ('\0'), abbreviated NUL.
+ *
+ * @since 3.6
+ */
+ public static final char NUL = '\0';
+
+ static {
+ ArrayUtils.setAll(CHAR_STRING_ARRAY, i -> String.valueOf((char) i));
+ }
+
+ /**
+ * {@link CharUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code CharUtils.toString('c');}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public CharUtils() {
+ }
+
+ /**
+ * Converts the character to a Character.
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same Character object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toCharacterObject(' ') = ' '
+ * CharUtils.toCharacterObject('A') = 'A'
+ * </pre>
+ *
+ * @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127.
+ * @param ch the character to convert
+ * @return a Character of the specified character
+ */
+ @Deprecated
+ public static Character toCharacterObject(final char ch) {
+ return Character.valueOf(ch);
+ }
+
+ /**
+ * Converts the String to a Character using the first character, returning
+ * null for empty Strings.
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same Character object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toCharacterObject(null) = null
+ * CharUtils.toCharacterObject("") = null
+ * CharUtils.toCharacterObject("A") = 'A'
+ * CharUtils.toCharacterObject("BA") = 'B'
+ * </pre>
+ *
+ * @param str the character to convert
+ * @return the Character value of the first letter of the String
+ */
+ public static Character toCharacterObject(final String str) {
+ return StringUtils.isEmpty(str) ? null : Character.valueOf(str.charAt(0));
+ }
+
+ /**
+ * Converts the Character to a char throwing an exception for {@code null}.
+ *
+ * <pre>
+ * CharUtils.toChar(' ') = ' '
+ * CharUtils.toChar('A') = 'A'
+ * CharUtils.toChar(null) throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the char value of the Character
+ * @throws NullPointerException if the Character is null
+ */
+ public static char toChar(final Character ch) {
+ return Objects.requireNonNull(ch, "ch").charValue();
+ }
+
+ /**
+ * Converts the Character to a char handling {@code null}.
+ *
+ * <pre>
+ * CharUtils.toChar(null, 'X') = 'X'
+ * CharUtils.toChar(' ', 'X') = ' '
+ * CharUtils.toChar('A', 'X') = 'A'
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the value to use if the Character is null
+ * @return the char value of the Character or the default if null
+ */
+ public static char toChar(final Character ch, final char defaultValue) {
+ return ch != null ? ch.charValue() : defaultValue;
+ }
+
+ /**
+ * Converts the String to a char using the first character, throwing
+ * an exception on empty Strings.
+ *
+ * <pre>
+ * CharUtils.toChar("A") = 'A'
+ * CharUtils.toChar("BA") = 'B'
+ * CharUtils.toChar(null) throws IllegalArgumentException
+ * CharUtils.toChar("") throws IllegalArgumentException
+ * </pre>
+ *
+ * @param str the character to convert
+ * @return the char value of the first letter of the String
+ * @throws NullPointerException if the string is null
+ * @throws IllegalArgumentException if the String is empty
+ */
+ public static char toChar(final String str) {
+ Validate.notEmpty(str, "The String must not be empty");
+ return str.charAt(0);
+ }
+
+ /**
+ * Converts the String to a char using the first character, defaulting
+ * the value on empty Strings.
+ *
+ * <pre>
+ * CharUtils.toChar(null, 'X') = 'X'
+ * CharUtils.toChar("", 'X') = 'X'
+ * CharUtils.toChar("A", 'X') = 'A'
+ * CharUtils.toChar("BA", 'X') = 'B'
+ * </pre>
+ *
+ * @param str the character to convert
+ * @param defaultValue the value to use if the Character is null
+ * @return the char value of the first letter of the String or the default if null
+ */
+ public static char toChar(final String str, final char defaultValue) {
+ return StringUtils.isEmpty(str) ? defaultValue : str.charAt(0);
+ }
+
+ /**
+ * Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.
+ *
+ * <p>This method converts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3') = 3
+ * CharUtils.toIntValue('A') throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the int value of the character
+ * @throws IllegalArgumentException if the character is not ASCII numeric
+ */
+ public static int toIntValue(final char ch) {
+ if (!isAsciiNumeric(ch)) {
+ throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'");
+ }
+ return ch - 48;
+ }
+
+ /**
+ * Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.
+ *
+ * <p>This method converts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3', -1) = 3
+ * CharUtils.toIntValue('A', -1) = -1
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the default value to use if the character is not numeric
+ * @return the int value of the character
+ */
+ public static int toIntValue(final char ch, final int defaultValue) {
+ return isAsciiNumeric(ch) ? ch - 48 : defaultValue;
+ }
+
+ /**
+ * Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.
+ *
+ * <p>This method converts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue('3') = 3
+ * CharUtils.toIntValue(null) throws IllegalArgumentException
+ * CharUtils.toIntValue('A') throws IllegalArgumentException
+ * </pre>
+ *
+ * @param ch the character to convert, not null
+ * @return the int value of the character
+ * @throws NullPointerException if the Character is null
+ * @throws IllegalArgumentException if the Character is not ASCII numeric
+ */
+ public static int toIntValue(final Character ch) {
+ return toIntValue(toChar(ch));
+ }
+
+ /**
+ * Converts the character to the Integer it represents, throwing an
+ * exception if the character is not numeric.
+ *
+ * <p>This method converts the char '1' to the int 1 and so on.</p>
+ *
+ * <pre>
+ * CharUtils.toIntValue(null, -1) = -1
+ * CharUtils.toIntValue('3', -1) = 3
+ * CharUtils.toIntValue('A', -1) = -1
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @param defaultValue the default value to use if the character is not numeric
+ * @return the int value of the character
+ */
+ public static int toIntValue(final Character ch, final int defaultValue) {
+ return ch != null ? toIntValue(ch.charValue(), defaultValue) : defaultValue;
+ }
+
+ /**
+ * Converts the character to a String that contains the one character.
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same String object each time.</p>
+ *
+ * <pre>
+ * CharUtils.toString(' ') = " "
+ * CharUtils.toString('A') = "A"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return a String containing the one specified character
+ */
+ public static String toString(final char ch) {
+ if (ch < CHAR_STRING_ARRAY.length) {
+ return CHAR_STRING_ARRAY[ch];
+ }
+ return String.valueOf(ch);
+ }
+
+ /**
+ * Converts the character to a String that contains the one character.
+ *
+ * <p>For ASCII 7 bit characters, this uses a cache that will return the
+ * same String object each time.</p>
+ *
+ * <p>If {@code null} is passed in, {@code null} will be returned.</p>
+ *
+ * <pre>
+ * CharUtils.toString(null) = null
+ * CharUtils.toString(' ') = " "
+ * CharUtils.toString('A') = "A"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return a String containing the one specified character
+ */
+ public static String toString(final Character ch) {
+ return ch != null ? toString(ch.charValue()) : null;
+ }
+
+ /**
+ * Converts the string to the Unicode format '\u0020'.
+ *
+ * <p>This format is the Java source code format.</p>
+ *
+ * <pre>
+ * CharUtils.unicodeEscaped(' ') = "\u0020"
+ * CharUtils.unicodeEscaped('A') = "\u0041"
+ * </pre>
+ *
+ * @param ch the character to convert
+ * @return the escaped Unicode string
+ */
+ public static String unicodeEscaped(final char ch) {
+ return "\\u" +
+ HEX_DIGITS[(ch >> 12) & 15] +
+ HEX_DIGITS[(ch >> 8) & 15] +
+ HEX_DIGITS[(ch >> 4) & 15] +
+ HEX_DIGITS[(ch) & 15];
+ }
+
+ /**
+ * Converts the string to the Unicode format '\u0020'.
+ *
+ * <p>This format is the Java source code format.</p>
+ *
+ * <p>If {@code null} is passed in, {@code null} will be returned.</p>
+ *
+ * <pre>
+ * CharUtils.unicodeEscaped(null) = null
+ * CharUtils.unicodeEscaped(' ') = "\u0020"
+ * CharUtils.unicodeEscaped('A') = "\u0041"
+ * </pre>
+ *
+ * @param ch the character to convert, may be null
+ * @return the escaped Unicode string, null if null input
+ */
+ public static String unicodeEscaped(final Character ch) {
+ return ch != null ? unicodeEscaped(ch.charValue()) : null;
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit.
+ *
+ * <pre>
+ * CharUtils.isAscii('a') = true
+ * CharUtils.isAscii('A') = true
+ * CharUtils.isAscii('3') = true
+ * CharUtils.isAscii('-') = true
+ * CharUtils.isAscii('\n') = true
+ * CharUtils.isAscii('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if less than 128
+ */
+ public static boolean isAscii(final char ch) {
+ return ch < 128;
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit printable.
+ *
+ * <pre>
+ * CharUtils.isAsciiPrintable('a') = true
+ * CharUtils.isAsciiPrintable('A') = true
+ * CharUtils.isAsciiPrintable('3') = true
+ * CharUtils.isAsciiPrintable('-') = true
+ * CharUtils.isAsciiPrintable('\n') = false
+ * CharUtils.isAsciiPrintable('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 32 and 126 inclusive
+ */
+ public static boolean isAsciiPrintable(final char ch) {
+ return ch >= 32 && ch < 127;
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit control.
+ *
+ * <pre>
+ * CharUtils.isAsciiControl('a') = false
+ * CharUtils.isAsciiControl('A') = false
+ * CharUtils.isAsciiControl('3') = false
+ * CharUtils.isAsciiControl('-') = false
+ * CharUtils.isAsciiControl('\n') = true
+ * CharUtils.isAsciiControl('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if less than 32 or equals 127
+ */
+ public static boolean isAsciiControl(final char ch) {
+ return ch < 32 || ch == 127;
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit alphabetic.
+ *
+ * <pre>
+ * CharUtils.isAsciiAlpha('a') = true
+ * CharUtils.isAsciiAlpha('A') = true
+ * CharUtils.isAsciiAlpha('3') = false
+ * CharUtils.isAsciiAlpha('-') = false
+ * CharUtils.isAsciiAlpha('\n') = false
+ * CharUtils.isAsciiAlpha('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 65 and 90 or 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlpha(final char ch) {
+ return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch);
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit alphabetic upper case.
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphaUpper('a') = false
+ * CharUtils.isAsciiAlphaUpper('A') = true
+ * CharUtils.isAsciiAlphaUpper('3') = false
+ * CharUtils.isAsciiAlphaUpper('-') = false
+ * CharUtils.isAsciiAlphaUpper('\n') = false
+ * CharUtils.isAsciiAlphaUpper('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 65 and 90 inclusive
+ */
+ public static boolean isAsciiAlphaUpper(final char ch) {
+ return ch >= 'A' && ch <= 'Z';
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit alphabetic lower case.
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphaLower('a') = true
+ * CharUtils.isAsciiAlphaLower('A') = false
+ * CharUtils.isAsciiAlphaLower('3') = false
+ * CharUtils.isAsciiAlphaLower('-') = false
+ * CharUtils.isAsciiAlphaLower('\n') = false
+ * CharUtils.isAsciiAlphaLower('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlphaLower(final char ch) {
+ return ch >= 'a' && ch <= 'z';
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit numeric.
+ *
+ * <pre>
+ * CharUtils.isAsciiNumeric('a') = false
+ * CharUtils.isAsciiNumeric('A') = false
+ * CharUtils.isAsciiNumeric('3') = true
+ * CharUtils.isAsciiNumeric('-') = false
+ * CharUtils.isAsciiNumeric('\n') = false
+ * CharUtils.isAsciiNumeric('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 48 and 57 inclusive
+ */
+ public static boolean isAsciiNumeric(final char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ /**
+ * Checks whether the character is ASCII 7 bit numeric.
+ *
+ * <pre>
+ * CharUtils.isAsciiAlphanumeric('a') = true
+ * CharUtils.isAsciiAlphanumeric('A') = true
+ * CharUtils.isAsciiAlphanumeric('3') = true
+ * CharUtils.isAsciiAlphanumeric('-') = false
+ * CharUtils.isAsciiAlphanumeric('\n') = false
+ * CharUtils.isAsciiAlphanumeric('&copy;') = false
+ * </pre>
+ *
+ * @param ch the character to check
+ * @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
+ */
+ public static boolean isAsciiAlphanumeric(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;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/Charsets.java b/src/main/java/org/apache/commons/lang3/Charsets.java
new file mode 100644
index 000000000..c80bb90a3
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Charsets.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * Internal use only.
+ * <p>
+ * Provides utilities for {@link Charset}.
+ * </p>
+ * <p>
+ * Package private since Apache Commons IO already provides a Charsets because {@link Charset} is in
+ * {@code java.nio.charset}.
+ * </p>
+ *
+ * @since 3.10
+ */
+class Charsets {
+
+ /**
+ * Returns the given {@code charset} or the default Charset if {@code charset} is null.
+ *
+ * @param charset a Charset or null.
+ * @return the given {@code charset} or the default Charset if {@code charset} is null.
+ */
+ static Charset toCharset(final Charset charset) {
+ return charset == null ? Charset.defaultCharset() : charset;
+ }
+
+ /**
+ * Returns the given {@code charset} or the default Charset if {@code charset} is null.
+ *
+ * @param charsetName a Charset or null.
+ * @return the given {@code charset} or the default Charset if {@code charset} is null.
+ * @throws UnsupportedCharsetException If no support for the named charset is available in this instance of the Java
+ * virtual machine
+ */
+ static Charset toCharset(final String charsetName) {
+ return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName);
+ }
+
+ /**
+ * Returns the given {@code charset} or the default Charset if {@code charset} is null.
+ *
+ * @param charsetName a Charset or null.
+ * @return the given {@code charset} or the default Charset if {@code charset} is null.
+ */
+ static String toCharsetName(final String charsetName) {
+ return charsetName == null ? Charset.defaultCharset().name() : charsetName;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java b/src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java
new file mode 100644
index 000000000..a87156a08
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ClassLoaderUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import java.net.URLClassLoader;
+import java.util.Arrays;
+
+/**
+ * Helps work with {@link ClassLoader}.
+ *
+ * @since 3.10
+ */
+public class ClassLoaderUtils {
+
+ /**
+ * Converts the given class loader to a String calling {@link #toString(URLClassLoader)}.
+ *
+ * @param classLoader to URLClassLoader to convert.
+ * @return the formatted string.
+ */
+ public static String toString(final ClassLoader classLoader) {
+ if (classLoader instanceof URLClassLoader) {
+ return toString((URLClassLoader) classLoader);
+ }
+ return classLoader.toString();
+ }
+
+ /**
+ * Converts the given URLClassLoader to a String in the format
+ * {@code "URLClassLoader.toString() + [URL1, URL2, ...]"}.
+ *
+ * @param classLoader to URLClassLoader to convert.
+ * @return the formatted string.
+ */
+ public static String toString(final URLClassLoader classLoader) {
+ return classLoader + Arrays.toString(classLoader.getURLs());
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/ClassPathUtils.java b/src/main/java/org/apache/commons/lang3/ClassPathUtils.java
new file mode 100644
index 000000000..df0efa255
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ClassPathUtils.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Objects;
+
+/**
+ * Operations regarding the classpath.
+ *
+ * <p>
+ * The methods of this class do not allow {@code null} inputs.
+ * </p>
+ *
+ * @since 3.3
+ */
+//@Immutable
+public class ClassPathUtils {
+
+ /**
+ * Converts a package name to a Java path ('/').
+ *
+ * @param path the source path.
+ * @return a package name.
+ * @since 3.13.0
+ */
+ public static String packageToPath(final String path) {
+ return Objects.requireNonNull(path, "path").replace('.', '/');
+ }
+
+ /**
+ * Converts a Java path ('/') to a package name.
+ *
+ * @param path the source path.
+ * @return a package name.
+ * @since 3.13.0
+ */
+ public static String pathToPackage(final String path) {
+ return Objects.requireNonNull(path, "path").replace('/', '.');
+ }
+
+ /**
+ * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context.
+ *
+ * <p>
+ * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed.
+ * </p>
+ *
+ * <pre>
+ * ClassPathUtils.toFullyQualifiedName(StringUtils.class, "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
+ * </pre>
+ *
+ * @param context The context for constructing the name.
+ * @param resourceName the resource name to construct the fully qualified name for.
+ * @return the fully qualified name of the resource with name {@code resourceName}.
+ * @throws NullPointerException if either {@code context} or {@code resourceName} is null.
+ */
+ public static String toFullyQualifiedName(final Class<?> context, final String resourceName) {
+ Objects.requireNonNull(context, "context");
+ Objects.requireNonNull(resourceName, "resourceName");
+ return toFullyQualifiedName(context.getPackage(), resourceName);
+ }
+
+ /**
+ * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context.
+ *
+ * <p>
+ * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed.
+ * </p>
+ *
+ * <pre>
+ * ClassPathUtils.toFullyQualifiedName(StringUtils.class.getPackage(), "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
+ * </pre>
+ *
+ * @param context The context for constructing the name.
+ * @param resourceName the resource name to construct the fully qualified name for.
+ * @return the fully qualified name of the resource with name {@code resourceName}.
+ * @throws NullPointerException if either {@code context} or {@code resourceName} is null.
+ */
+ public static String toFullyQualifiedName(final Package context, final String resourceName) {
+ Objects.requireNonNull(context, "context");
+ Objects.requireNonNull(resourceName, "resourceName");
+ return context.getName() + "." + resourceName;
+ }
+
+ /**
+ * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context.
+ *
+ * <p>
+ * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed.
+ * </p>
+ *
+ * <pre>
+ * ClassPathUtils.toFullyQualifiedPath(StringUtils.class, "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
+ * </pre>
+ *
+ * @param context The context for constructing the path.
+ * @param resourceName the resource name to construct the fully qualified path for.
+ * @return the fully qualified path of the resource with name {@code resourceName}.
+ * @throws NullPointerException if either {@code context} or {@code resourceName} is null.
+ */
+ public static String toFullyQualifiedPath(final Class<?> context, final String resourceName) {
+ Objects.requireNonNull(context, "context");
+ Objects.requireNonNull(resourceName, "resourceName");
+ return toFullyQualifiedPath(context.getPackage(), resourceName);
+ }
+
+ /**
+ * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context.
+ *
+ * <p>
+ * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed.
+ * </p>
+ *
+ * <pre>
+ * ClassPathUtils.toFullyQualifiedPath(StringUtils.class.getPackage(), "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
+ * </pre>
+ *
+ * @param context The context for constructing the path.
+ * @param resourceName the resource name to construct the fully qualified path for.
+ * @return the fully qualified path of the resource with name {@code resourceName}.
+ * @throws NullPointerException if either {@code context} or {@code resourceName} is null.
+ */
+ public static String toFullyQualifiedPath(final Package context, final String resourceName) {
+ Objects.requireNonNull(context, "context");
+ Objects.requireNonNull(resourceName, "resourceName");
+ return packageToPath(context.getName()) + "/" + resourceName;
+ }
+
+ /**
+ * {@link ClassPathUtils} instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code ClassPathUtils.toFullyQualifiedName(MyClass.class, "MyClass.properties");}.
+ *
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public ClassPathUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java b/src/main/java/org/apache/commons/lang3/ClassUtils.java
new file mode 100644
index 000000000..1d2b9c461
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java
@@ -0,0 +1,1623 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.mutable.MutableObject;
+
+/**
+ * Operates on classes without using reflection.
+ *
+ * <p>
+ * This class handles invalid {@code null} inputs as best it can. Each method documents its behavior in more detail.
+ * </p>
+ *
+ * <p>
+ * The notion of a {@code canonical name} includes the human readable name for the type, for example {@code int[]}. The
+ * non-canonical method variants work with the JVM names, such as {@code [I}.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class ClassUtils {
+
+ /**
+ * Inclusivity literals for {@link #hierarchy(Class, Interfaces)}.
+ *
+ * @since 3.2
+ */
+ public enum Interfaces {
+
+ /** Includes interfaces. */
+ INCLUDE,
+
+ /** Excludes interfaces. */
+ EXCLUDE
+ }
+
+ private static final Comparator<Class<?>> COMPARATOR = (o1, o2) -> Objects.compare(getName(o1), getName(o2), String::compareTo);
+
+ /**
+ * The package separator character: {@code '&#x2e;' == {@value}}.
+ */
+ public static final char PACKAGE_SEPARATOR_CHAR = '.';
+
+ /**
+ * The package separator String: {@code "&#x2e;"}.
+ */
+ public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR);
+
+ /**
+ * The inner class separator character: {@code '$' == {@value}}.
+ */
+ public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
+
+ /**
+ * The inner class separator String: {@code "$"}.
+ */
+ public static final String INNER_CLASS_SEPARATOR = String.valueOf(INNER_CLASS_SEPARATOR_CHAR);
+
+ /**
+ * Maps names of primitives to their corresponding primitive {@link Class}es.
+ */
+ private static final Map<String, Class<?>> namePrimitiveMap = new HashMap<>();
+
+ static {
+ namePrimitiveMap.put("boolean", Boolean.TYPE);
+ namePrimitiveMap.put("byte", Byte.TYPE);
+ namePrimitiveMap.put("char", Character.TYPE);
+ namePrimitiveMap.put("short", Short.TYPE);
+ namePrimitiveMap.put("int", Integer.TYPE);
+ namePrimitiveMap.put("long", Long.TYPE);
+ namePrimitiveMap.put("double", Double.TYPE);
+ namePrimitiveMap.put("float", Float.TYPE);
+ namePrimitiveMap.put("void", Void.TYPE);
+ }
+
+ /**
+ * Maps primitive {@link Class}es to their corresponding wrapper {@link Class}.
+ */
+ private static final Map<Class<?>, Class<?>> primitiveWrapperMap = new HashMap<>();
+
+ static {
+ primitiveWrapperMap.put(Boolean.TYPE, Boolean.class);
+ primitiveWrapperMap.put(Byte.TYPE, Byte.class);
+ primitiveWrapperMap.put(Character.TYPE, Character.class);
+ primitiveWrapperMap.put(Short.TYPE, Short.class);
+ primitiveWrapperMap.put(Integer.TYPE, Integer.class);
+ primitiveWrapperMap.put(Long.TYPE, Long.class);
+ primitiveWrapperMap.put(Double.TYPE, Double.class);
+ primitiveWrapperMap.put(Float.TYPE, Float.class);
+ primitiveWrapperMap.put(Void.TYPE, Void.TYPE);
+ }
+
+ /**
+ * Maps wrapper {@link Class}es to their corresponding primitive types.
+ */
+ private static final Map<Class<?>, Class<?>> wrapperPrimitiveMap = new HashMap<>();
+
+ static {
+ primitiveWrapperMap.forEach((primitiveClass, wrapperClass) -> {
+ if (!primitiveClass.equals(wrapperClass)) {
+ wrapperPrimitiveMap.put(wrapperClass, primitiveClass);
+ }
+ });
+ }
+
+ /**
+ * Maps a primitive class name to its corresponding abbreviation used in array class names.
+ */
+ private static final Map<String, String> abbreviationMap;
+
+ /**
+ * Maps an abbreviation used in array class names to corresponding primitive class name.
+ */
+ private static final Map<String, String> reverseAbbreviationMap;
+
+ /** Feed abbreviation maps. */
+ static {
+ final Map<String, String> map = new HashMap<>();
+ map.put("int", "I");
+ map.put("boolean", "Z");
+ map.put("float", "F");
+ map.put("long", "J");
+ map.put("short", "S");
+ map.put("byte", "B");
+ map.put("double", "D");
+ map.put("char", "C");
+ abbreviationMap = Collections.unmodifiableMap(map);
+ reverseAbbreviationMap = Collections.unmodifiableMap(map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
+ }
+
+ /**
+ * Gets the class comparator, comparing by class name.
+ *
+ * @return the class comparator.
+ * @since 3.13.0
+ */
+ public static Comparator<Class<?>> comparator() {
+ return COMPARATOR;
+ }
+
+ /**
+ * Given a {@link List} of {@link Class} objects, this method converts them into class names.
+ *
+ * <p>
+ * A new {@link List} is returned. {@code null} objects will be copied into the returned list as {@code null}.
+ * </p>
+ *
+ * @param classes the classes to change
+ * @return a {@link List} of class names corresponding to the Class objects, {@code null} if null input
+ * @throws ClassCastException if {@code classes} contains a non-{@link Class} entry
+ */
+ public static List<String> convertClassesToClassNames(final List<Class<?>> classes) {
+ return classes == null ? null : classes.stream().map(e -> getName(e, null)).collect(Collectors.toList());
+ }
+
+ /**
+ * Given a {@link List} of class names, this method converts them into classes.
+ *
+ * <p>
+ * A new {@link List} is returned. If the class name cannot be found, {@code null} is stored in the {@link List}. If the
+ * class name in the {@link List} is {@code null}, {@code null} is stored in the output {@link List}.
+ * </p>
+ *
+ * @param classNames the classNames to change
+ * @return a {@link List} of Class objects corresponding to the class names, {@code null} if null input
+ * @throws ClassCastException if classNames contains a non String entry
+ */
+ public static List<Class<?>> convertClassNamesToClasses(final List<String> classNames) {
+ if (classNames == null) {
+ return null;
+ }
+ final List<Class<?>> classes = new ArrayList<>(classNames.size());
+ classNames.forEach(className -> {
+ try {
+ classes.add(Class.forName(className));
+ } catch (final Exception ex) {
+ classes.add(null);
+ }
+ });
+ return classes;
+ }
+
+ /**
+ * Gets the abbreviated name of a {@link Class}.
+ *
+ * @param cls the class to get the abbreviated name for, may be {@code null}
+ * @param lengthHint the desired length of the abbreviated name
+ * @return the abbreviated name or an empty string
+ * @throws IllegalArgumentException if len &lt;= 0
+ * @see #getAbbreviatedName(String, int)
+ * @since 3.4
+ */
+ public static String getAbbreviatedName(final Class<?> cls, final int lengthHint) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getAbbreviatedName(cls.getName(), lengthHint);
+ }
+
+ /**
+ * Gets the abbreviated class name from a {@link String}.
+ *
+ * <p>
+ * The string passed in is assumed to be a class name - it is not checked.
+ * </p>
+ *
+ * <p>
+ * The abbreviation algorithm will shorten the class name, usually without significant loss of meaning.
+ * </p>
+ *
+ * <p>
+ * The abbreviated class name will always include the complete package hierarchy. If enough space is available,
+ * rightmost sub-packages will be displayed in full length. The abbreviated package names will be shortened to a single
+ * character.
+ * </p>
+ * <p>
+ * Only package names are shortened, the class simple name remains untouched. (See examples.)
+ * </p>
+ * <p>
+ * The result will be longer than the desired length only if all the package names shortened to a single character plus
+ * the class simple name with the separating dots together are longer than the desired length. In other words, when the
+ * class name cannot be shortened to the desired length.
+ * </p>
+ * <p>
+ * If the class name can be shortened then the final length will be at most {@code lengthHint} characters.
+ * </p>
+ * <p>
+ * If the {@code lengthHint} is zero or negative then the method throws exception. If you want to achieve the shortest
+ * possible version then use {@code 1} as a {@code lengthHint}.
+ * </p>
+ *
+ * <table>
+ * <caption>Examples</caption>
+ * <tr>
+ * <td>className</td>
+ * <td>len</td>
+ * <td>return</td>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>1</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"java.lang.String"</td>
+ * <td>5</td>
+ * <td>"j.l.String"</td>
+ * </tr>
+ * <tr>
+ * <td>"java.lang.String"</td>
+ * <td>15</td>
+ * <td>"j.lang.String"</td>
+ * </tr>
+ * <tr>
+ * <td>"java.lang.String"</td>
+ * <td>30</td>
+ * <td>"java.lang.String"</td>
+ * </tr>
+ * <tr>
+ * <td>"org.apache.commons.lang3.ClassUtils"</td>
+ * <td>18</td>
+ * <td>"o.a.c.l.ClassUtils"</td>
+ * </tr>
+ * </table>
+ *
+ * @param className the className to get the abbreviated name for, may be {@code null}
+ * @param lengthHint the desired length of the abbreviated name
+ * @return the abbreviated name or an empty string if the specified class name is {@code null} or empty string. The
+ * abbreviated name may be longer than the desired length if it cannot be abbreviated to the desired length.
+ * @throws IllegalArgumentException if {@code len <= 0}
+ * @since 3.4
+ */
+ public static String getAbbreviatedName(final String className, final int lengthHint) {
+ if (lengthHint <= 0) {
+ throw new IllegalArgumentException("len must be > 0");
+ }
+ if (className == null) {
+ return StringUtils.EMPTY;
+ }
+ if (className.length() <= lengthHint) {
+ return className;
+ }
+ final char[] abbreviated = className.toCharArray();
+ int target = 0;
+ int source = 0;
+ while (source < abbreviated.length) {
+ // copy the next part
+ int runAheadTarget = target;
+ while (source < abbreviated.length && abbreviated[source] != '.') {
+ abbreviated[runAheadTarget++] = abbreviated[source++];
+ }
+
+ ++target;
+ if (useFull(runAheadTarget, source, abbreviated.length, lengthHint) || target > runAheadTarget) {
+ target = runAheadTarget;
+ }
+
+ // copy the '.' unless it was the last part
+ if (source < abbreviated.length) {
+ abbreviated[target++] = abbreviated[source++];
+ }
+ }
+ return new String(abbreviated, 0, target);
+ }
+
+ /**
+ * Gets a {@link List} of all interfaces implemented by the given class and its superclasses.
+ *
+ * <p>
+ * The order is determined by looking through each interface in turn as declared in the source file and following its
+ * hierarchy up. Then each superclass is considered in the same way. Later duplicates are ignored, so the order is
+ * maintained.
+ * </p>
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @return the {@link List} of interfaces in order, {@code null} if null input
+ */
+ public static List<Class<?>> getAllInterfaces(final Class<?> cls) {
+ if (cls == null) {
+ return null;
+ }
+
+ final LinkedHashSet<Class<?>> interfacesFound = new LinkedHashSet<>();
+ getAllInterfaces(cls, interfacesFound);
+
+ return new ArrayList<>(interfacesFound);
+ }
+
+ /**
+ * Gets the interfaces for the specified class.
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @param interfacesFound the {@link Set} of interfaces for the class
+ */
+ private static void getAllInterfaces(Class<?> cls, final HashSet<Class<?>> interfacesFound) {
+ while (cls != null) {
+ final Class<?>[] interfaces = cls.getInterfaces();
+
+ for (final Class<?> i : interfaces) {
+ if (interfacesFound.add(i)) {
+ getAllInterfaces(i, interfacesFound);
+ }
+ }
+
+ cls = cls.getSuperclass();
+ }
+ }
+
+ /**
+ * Gets a {@link List} of superclasses for the given class.
+ *
+ * @param cls the class to look up, may be {@code null}
+ * @return the {@link List} of superclasses in order going up from this one {@code null} if null input
+ */
+ public static List<Class<?>> getAllSuperclasses(final Class<?> cls) {
+ if (cls == null) {
+ return null;
+ }
+ final List<Class<?>> classes = new ArrayList<>();
+ Class<?> superclass = cls.getSuperclass();
+ while (superclass != null) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes;
+ }
+
+ /**
+ * Gets the canonical class name for a {@link Class}.
+ *
+ * @param cls the class for which to get the canonical class name; may be null
+ * @return the canonical name of the class, or the empty String
+ * @since 3.7
+ * @see Class#getCanonicalName()
+ */
+ public static String getCanonicalName(final Class<?> cls) {
+ return getCanonicalName(cls, StringUtils.EMPTY);
+ }
+
+ /**
+ * Gets the canonical name for a {@link Class}.
+ *
+ * @param cls the class for which to get the canonical class name; may be null
+ * @param valueIfNull the return value if null
+ * @return the canonical name of the class, or {@code valueIfNull}
+ * @since 3.7
+ * @see Class#getCanonicalName()
+ */
+ public static String getCanonicalName(final Class<?> cls, final String valueIfNull) {
+ if (cls == null) {
+ return valueIfNull;
+ }
+ final String canonicalName = cls.getCanonicalName();
+ return canonicalName == null ? valueIfNull : canonicalName;
+ }
+
+ /**
+ * Gets the canonical name for an {@link Object}.
+ *
+ * @param object the object for which to get the canonical class name; may be null
+ * @return the canonical name of the object, or the empty String
+ * @since 3.7
+ * @see Class#getCanonicalName()
+ */
+ public static String getCanonicalName(final Object object) {
+ return getCanonicalName(object, StringUtils.EMPTY);
+ }
+
+ /**
+ * Gets the canonical name for an {@link Object}.
+ *
+ * @param object the object for which to get the canonical class name; may be null
+ * @param valueIfNull the return value if null
+ * @return the canonical name of the object or {@code valueIfNull}
+ * @since 3.7
+ * @see Class#getCanonicalName()
+ */
+ public static String getCanonicalName(final Object object, final String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ final String canonicalName = object.getClass().getCanonicalName();
+ return canonicalName == null ? valueIfNull : canonicalName;
+ }
+
+ /**
+ * Converts a given name of class into canonical format. If name of class is not a name of array class it returns
+ * unchanged name.
+ *
+ * <p>
+ * The method does not change the {@code $} separators in case the class is inner class.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * <ul>
+ * <li>{@code getCanonicalName("[I") = "int[]"}</li>
+ * <li>{@code getCanonicalName("[Ljava.lang.String;") = "java.lang.String[]"}</li>
+ * <li>{@code getCanonicalName("java.lang.String") = "java.lang.String"}</li>
+ * </ul>
+ * </p>
+ *
+ * @param className the name of class
+ * @return canonical form of class name
+ * @since 2.4
+ */
+ private static String getCanonicalName(String className) {
+ className = StringUtils.deleteWhitespace(className);
+ if (className == null) {
+ return null;
+ }
+ int dim = 0;
+ while (className.startsWith("[")) {
+ dim++;
+ className = className.substring(1);
+ }
+ if (dim < 1) {
+ return className;
+ }
+ if (className.startsWith("L")) {
+ className = className.substring(1, className.endsWith(";") ? className.length() - 1 : className.length());
+ } else if (!className.isEmpty()) {
+ className = reverseAbbreviationMap.get(className.substring(0, 1));
+ }
+ final StringBuilder canonicalClassNameBuffer = new StringBuilder(className);
+ for (int i = 0; i < dim; i++) {
+ canonicalClassNameBuffer.append("[]");
+ }
+ return canonicalClassNameBuffer.toString();
+ }
+
+ /**
+ * Returns the (initialized) class represented by {@code className} using the {@code classLoader}. This implementation
+ * supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
+ * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param classLoader the class loader to use to load the class
+ * @param className the class name
+ * @return the class represented by {@code className} using the {@code classLoader}
+ * @throws NullPointerException if the className is null
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException {
+ return getClass(classLoader, className, true);
+ }
+
+ /**
+ * Returns the class represented by {@code className} using the {@code classLoader}. This implementation supports the
+ * syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and
+ * "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param classLoader the class loader to use to load the class
+ * @param className the class name
+ * @param initialize whether the class must be initialized
+ * @return the class represented by {@code className} using the {@code classLoader}
+ * @throws NullPointerException if the className is null
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException {
+ try {
+ Class<?> clazz = namePrimitiveMap.get(className);
+ return clazz != null ? clazz : Class.forName(toCanonicalName(className), initialize, classLoader);
+ } catch (final ClassNotFoundException ex) {
+ // allow path separators (.) as inner class name separators
+ final int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+
+ if (lastDotIndex != -1) {
+ try {
+ return getClass(classLoader, className.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR_CHAR + className.substring(lastDotIndex + 1),
+ initialize);
+ } catch (final ClassNotFoundException ignored) {
+ // ignore exception
+ }
+ }
+
+ throw ex;
+ }
+ }
+
+ /**
+ * Returns the (initialized) class represented by {@code className} using the current thread's context class loader.
+ * This implementation supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
+ * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param className the class name
+ * @return the class represented by {@code className} using the current thread's context class loader
+ * @throws NullPointerException if the className is null
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(final String className) throws ClassNotFoundException {
+ return getClass(className, true);
+ }
+
+ /**
+ * Returns the class represented by {@code className} using the current thread's context class loader. This
+ * implementation supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
+ * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
+ *
+ * @param className the class name
+ * @param initialize whether the class must be initialized
+ * @return the class represented by {@code className} using the current thread's context class loader
+ * @throws NullPointerException if the className is null
+ * @throws ClassNotFoundException if the class is not found
+ */
+ public static Class<?> getClass(final String className, final boolean initialize) throws ClassNotFoundException {
+ final ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
+ final ClassLoader loader = contextCL == null ? ClassUtils.class.getClassLoader() : contextCL;
+ return getClass(loader, className, initialize);
+ }
+
+ /**
+ * Delegates to {@link Class#getComponentType()} using generics.
+ *
+ * @param <T> The array class type.
+ * @param cls A class or null.
+ * @return The array component type or null.
+ * @see Class#getComponentType()
+ * @since 3.13.0
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> getComponentType(final Class<T[]> cls) {
+ return cls == null ? null : (Class<T>) cls.getComponentType();
+ }
+
+ /**
+ * Null-safe version of {@code cls.getName()}
+ *
+ * @param cls the class for which to get the class name; may be null
+ * @return the class name or the empty string in case the argument is {@code null}
+ * @since 3.7
+ * @see Class#getSimpleName()
+ */
+ public static String getName(final Class<?> cls) {
+ return getName(cls, StringUtils.EMPTY);
+ }
+
+ /**
+ * Null-safe version of {@code cls.getName()}
+ *
+ * @param cls the class for which to get the class name; may be null
+ * @param valueIfNull the return value if the argument {@code cls} is {@code null}
+ * @return the class name or {@code valueIfNull}
+ * @since 3.7
+ * @see Class#getName()
+ */
+ public static String getName(final Class<?> cls, final String valueIfNull) {
+ return cls == null ? valueIfNull : cls.getName();
+ }
+
+ /**
+ * Null-safe version of {@code object.getClass().getName()}
+ *
+ * @param object the object for which to get the class name; may be null
+ * @return the class name or the empty String
+ * @since 3.7
+ * @see Class#getSimpleName()
+ */
+ public static String getName(final Object object) {
+ return getName(object, StringUtils.EMPTY);
+ }
+
+ /**
+ * Null-safe version of {@code object.getClass().getSimpleName()}
+ *
+ * @param object the object for which to get the class name; may be null
+ * @param valueIfNull the value to return if {@code object} is {@code null}
+ * @return the class name or {@code valueIfNull}
+ * @since 3.0
+ * @see Class#getName()
+ */
+ public static String getName(final Object object, final String valueIfNull) {
+ return object == null ? valueIfNull : object.getClass().getName();
+ }
+
+ /**
+ * Gets the package name from the canonical name of a {@link Class}.
+ *
+ * @param cls the class to get the package name for, may be {@code null}.
+ * @return the package name or an empty string
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(final Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getPackageCanonicalName(cls.getName());
+ }
+
+ /**
+ * Gets the package name from the class name of an {@link Object}.
+ *
+ * @param object the class to get the package name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the package name of the object, or the null value
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(final Object object, final String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getPackageCanonicalName(object.getClass().getName());
+ }
+
+ /**
+ * Gets the package name from the class name.
+ *
+ * <p>
+ * The string passed in is assumed to be a class name - it is not checked.
+ * </p>
+ * <p>
+ * If the class is in the default package, return an empty string.
+ * </p>
+ *
+ * @param name the name to get the package name for, may be {@code null}
+ * @return the package name or an empty string
+ * @since 2.4
+ */
+ public static String getPackageCanonicalName(final String name) {
+ return getPackageName(getCanonicalName(name));
+ }
+
+ /**
+ * Gets the package name of a {@link Class}.
+ *
+ * @param cls the class to get the package name for, may be {@code null}.
+ * @return the package name or an empty string
+ */
+ public static String getPackageName(final Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getPackageName(cls.getName());
+ }
+
+ /**
+ * Gets the package name of an {@link Object}.
+ *
+ * @param object the class to get the package name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the package name of the object, or the null value
+ */
+ public static String getPackageName(final Object object, final String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getPackageName(object.getClass());
+ }
+
+ /**
+ * Gets the package name from a {@link String}.
+ *
+ * <p>
+ * The string passed in is assumed to be a class name - it is not checked.
+ * </p>
+ * <p>
+ * If the class is unpackaged, return an empty string.
+ * </p>
+ *
+ * @param className the className to get the package name for, may be {@code null}
+ * @return the package name or an empty string
+ */
+ public static String getPackageName(String className) {
+ if (StringUtils.isEmpty(className)) {
+ return StringUtils.EMPTY;
+ }
+
+ // Strip array encoding
+ while (className.charAt(0) == '[') {
+ className = className.substring(1);
+ }
+ // Strip Object type encoding
+ if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
+ className = className.substring(1);
+ }
+
+ final int i = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+ if (i == -1) {
+ return StringUtils.EMPTY;
+ }
+ return className.substring(0, i);
+ }
+
+ /**
+ * Returns the desired Method much like {@code Class.getMethod}, however it ensures that the returned Method is from a
+ * public class or interface and not from an anonymous inner class. This means that the Method is invokable and doesn't
+ * fall foul of Java bug <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4071957">4071957</a>).
+ *
+ * <pre>
+ * <code>Set set = Collections.unmodifiableSet(...);
+ * Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty", new Class[0]);
+ * Object result = method.invoke(set, new Object[]);</code>
+ * </pre>
+ *
+ * @param cls the class to check, not null
+ * @param methodName the name of the method
+ * @param parameterTypes the list of parameters
+ * @return the method
+ * @throws NullPointerException if the class is null
+ * @throws SecurityException if a security violation occurred
+ * @throws NoSuchMethodException if the method is not found in the given class or if the method doesn't conform with the
+ * requirements
+ */
+ public static Method getPublicMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) throws NoSuchMethodException {
+
+ final Method declaredMethod = cls.getMethod(methodName, parameterTypes);
+ if (isPublic(declaredMethod.getDeclaringClass())) {
+ return declaredMethod;
+ }
+
+ final List<Class<?>> candidateClasses = new ArrayList<>(getAllInterfaces(cls));
+ candidateClasses.addAll(getAllSuperclasses(cls));
+
+ for (final Class<?> candidateClass : candidateClasses) {
+ if (!isPublic(candidateClass)) {
+ continue;
+ }
+ final Method candidateMethod;
+ try {
+ candidateMethod = candidateClass.getMethod(methodName, parameterTypes);
+ } catch (final NoSuchMethodException ex) {
+ continue;
+ }
+ if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) {
+ return candidateMethod;
+ }
+ }
+
+ throw new NoSuchMethodException("Can't find a public method for " + methodName + " " + ArrayUtils.toString(parameterTypes));
+ }
+
+ /**
+ * Gets the canonical name minus the package name from a {@link Class}.
+ *
+ * @param cls the class for which to get the short canonical class name; may be null
+ * @return the canonical name without the package name or an empty string
+ * @since 2.4
+ * @see Class#getCanonicalName()
+ */
+ public static String getShortCanonicalName(final Class<?> cls) {
+ return cls == null ? StringUtils.EMPTY : getShortCanonicalName(cls.getCanonicalName());
+ }
+
+ /**
+ * Gets the canonical name minus the package name for an {@link Object}.
+ *
+ * @param object the class to get the short name for, may be null
+ * @param valueIfNull the value to return if null
+ * @return the canonical name of the object without the package name, or the null value
+ * @since 2.4
+ * @see Class#getCanonicalName()
+ */
+ public static String getShortCanonicalName(final Object object, final String valueIfNull) {
+ return object == null ? valueIfNull : getShortCanonicalName(object.getClass().getCanonicalName());
+ }
+
+ /**
+ * Gets the canonical name minus the package name from a String.
+ *
+ * <p>
+ * The string passed in is assumed to be a class name - it is not checked.
+ * </p>
+ *
+ * <p>
+ * Note that this method is mainly designed to handle the arrays and primitives properly. If the class is an inner class
+ * then the result value will not contain the outer classes. This way the behavior of this method is different from
+ * {@link #getShortClassName(String)}. The argument in that case is class name and not canonical name and the return
+ * value retains the outer classes.
+ * </p>
+ *
+ * <p>
+ * Note that there is no way to reliably identify the part of the string representing the package hierarchy and the part
+ * that is the outer class or classes in case of an inner class. Trying to find the class would require reflective call
+ * and the class itself may not even be on the class path. Relying on the fact that class names start with capital
+ * letter and packages with lower case is heuristic.
+ * </p>
+ *
+ * <p>
+ * It is recommended to use {@link #getShortClassName(String)} for cases when the class is an inner class and use this
+ * method for cases it is designed for.
+ * </p>
+ *
+ * <table>
+ * <caption>Examples</caption>
+ * <tr>
+ * <td>return value</td>
+ * <td>input</td>
+ * </tr>
+ * <tr>
+ * <td>{@code ""}</td>
+ * <td>{@code (String)null}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "Map.Entry"}</td>
+ * <td>{@code java.util.Map.Entry.class.getName()}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "Entry"}</td>
+ * <td>{@code java.util.Map.Entry.class.getCanonicalName()}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "ClassUtils"}</td>
+ * <td>{@code "org.apache.commons.lang3.ClassUtils"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "ClassUtils[]"}</td>
+ * <td>{@code "[Lorg.apache.commons.lang3.ClassUtils;"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "ClassUtils[][]"}</td>
+ * <td>{@code "[[Lorg.apache.commons.lang3.ClassUtils;"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "ClassUtils[]"}</td>
+ * <td>{@code "org.apache.commons.lang3.ClassUtils[]"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "ClassUtils[][]"}</td>
+ * <td>{@code "org.apache.commons.lang3.ClassUtils[][]"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[]"}</td>
+ * <td>{@code "[I"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[]"}</td>
+ * <td>{@code int[].class.getCanonicalName()}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[]"}</td>
+ * <td>{@code int[].class.getName()}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[][]"}</td>
+ * <td>{@code "[[I"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[]"}</td>
+ * <td>{@code "int[]"}</td>
+ * </tr>
+ * <tr>
+ * <td>{@code "int[][]"}</td>
+ * <td>{@code "int[][]"}</td>
+ * </tr>
+ * </table>
+ *
+ * @param canonicalName the class name to get the short name for
+ * @return the canonical name of the class without the package name or an empty string
+ * @since 2.4
+ */
+ public static String getShortCanonicalName(final String canonicalName) {
+ return getShortClassName(getCanonicalName(canonicalName));
+ }
+
+ /**
+ * Gets the class name minus the package name from a {@link Class}.
+ *
+ * <p>
+ * This method simply gets the name using {@code Class.getName()} and then calls {@link #getShortClassName(Class)}. See
+ * relevant notes there.
+ * </p>
+ *
+ * @param cls the class to get the short name for.
+ * @return the class name without the package name or an empty string. If the class is an inner class then the returned
+ * value will contain the outer class or classes separated with {@code .} (dot) character.
+ */
+ public static String getShortClassName(final Class<?> cls) {
+ if (cls == null) {
+ return StringUtils.EMPTY;
+ }
+ return getShortClassName(cls.getName());
+ }
+
+ /**
+ * Gets the class name of the {@code object} without the package name or names.
+ *
+ * <p>
+ * The method looks up the class of the object and then converts the name of the class invoking
+ * {@link #getShortClassName(Class)} (see relevant notes there).
+ * </p>
+ *
+ * @param object the class to get the short name for, may be {@code null}
+ * @param valueIfNull the value to return if the object is {@code null}
+ * @return the class name of the object without the package name, or {@code valueIfNull} if the argument {@code object}
+ * is {@code null}
+ */
+ public static String getShortClassName(final Object object, final String valueIfNull) {
+ if (object == null) {
+ return valueIfNull;
+ }
+ return getShortClassName(object.getClass());
+ }
+
+ /**
+ * Gets the class name minus the package name from a String.
+ *
+ * <p>
+ * The string passed in is assumed to be a class name - it is not checked. The string has to be formatted the way as the
+ * JDK method {@code Class.getName()} returns it, and not the usual way as we write it, for example in import
+ * statements, or as it is formatted by {@code Class.getCanonicalName()}.
+ * </p>
+ *
+ * <p>
+ * The difference is is significant only in case of classes that are inner classes of some other classes. In this case
+ * the separator between the outer and inner class (possibly on multiple hierarchy level) has to be {@code $} (dollar
+ * sign) and not {@code .} (dot), as it is returned by {@code Class.getName()}
+ * </p>
+ *
+ * <p>
+ * Note that this method is called from the {@link #getShortClassName(Class)} method using the string returned by
+ * {@code Class.getName()}.
+ * </p>
+ *
+ * <p>
+ * Note that this method differs from {@link #getSimpleName(Class)} in that this will return, for example
+ * {@code "Map.Entry"} whilst the {@code java.lang.Class} variant will simply return {@code "Entry"}. In this example
+ * the argument {@code className} is the string {@code java.util.Map$Entry} (note the {@code $} sign.
+ * </p>
+ *
+ * @param className the className to get the short name for. It has to be formatted as returned by
+ * {@code Class.getName()} and not {@code Class.getCanonicalName()}
+ * @return the class name of the class without the package name or an empty string. If the class is an inner class then
+ * value contains the outer class or classes and the separator is replaced to be {@code .} (dot) character.
+ */
+ public static String getShortClassName(String className) {
+ if (StringUtils.isEmpty(className)) {
+ return StringUtils.EMPTY;
+ }
+
+ final StringBuilder arrayPrefix = new StringBuilder();
+
+ // Handle array encoding
+ if (className.startsWith("[")) {
+ while (className.charAt(0) == '[') {
+ className = className.substring(1);
+ arrayPrefix.append("[]");
+ }
+ // Strip Object type encoding
+ if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
+ className = className.substring(1, className.length() - 1);
+ }
+
+ if (reverseAbbreviationMap.containsKey(className)) {
+ className = reverseAbbreviationMap.get(className);
+ }
+ }
+
+ final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
+ final int innerIdx = className.indexOf(INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
+ String out = className.substring(lastDotIdx + 1);
+ if (innerIdx != -1) {
+ out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
+ }
+ return out + arrayPrefix;
+ }
+
+ /**
+ * Null-safe version of {@code cls.getSimpleName()}
+ *
+ * @param cls the class for which to get the simple name; may be null
+ * @return the simple class name or the empty string in case the argument is {@code null}
+ * @since 3.0
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(final Class<?> cls) {
+ return getSimpleName(cls, StringUtils.EMPTY);
+ }
+
+ /**
+ * Null-safe version of {@code cls.getSimpleName()}
+ *
+ * @param cls the class for which to get the simple name; may be null
+ * @param valueIfNull the value to return if null
+ * @return the simple class name or {@code valueIfNull} if the argument {@code cls} is {@code null}
+ * @since 3.0
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(final Class<?> cls, final String valueIfNull) {
+ return cls == null ? valueIfNull : cls.getSimpleName();
+ }
+
+ /**
+ * Null-safe version of {@code object.getClass().getSimpleName()}
+ *
+ * <p>
+ * It is to note that this method is overloaded and in case the argument {@code object} is a {@link Class} object then
+ * the {@link #getSimpleName(Class)} will be invoked. If this is a significant possibility then the caller should check
+ * this case and call {@code
+ * getSimpleName(Class.class)} or just simply use the string literal {@code "Class"}, which is the result of the method
+ * in that case.
+ * </p>
+ *
+ * @param object the object for which to get the simple class name; may be null
+ * @return the simple class name or the empty string in case the argument is {@code null}
+ * @since 3.7
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(final Object object) {
+ return getSimpleName(object, StringUtils.EMPTY);
+ }
+
+ /**
+ * Null-safe version of {@code object.getClass().getSimpleName()}
+ *
+ * @param object the object for which to get the simple class name; may be null
+ * @param valueIfNull the value to return if {@code object} is {@code null}
+ * @return the simple class name or {@code valueIfNull} if the argument {@code object} is {@code null}
+ * @since 3.0
+ * @see Class#getSimpleName()
+ */
+ public static String getSimpleName(final Object object, final String valueIfNull) {
+ return object == null ? valueIfNull : object.getClass().getSimpleName();
+ }
+
+ /**
+ * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order,
+ * excluding interfaces.
+ *
+ * @param type the type to get the class hierarchy from
+ * @return Iterable an Iterable over the class hierarchy of the given class
+ * @since 3.2
+ */
+ public static Iterable<Class<?>> hierarchy(final Class<?> type) {
+ return hierarchy(type, Interfaces.EXCLUDE);
+ }
+
+ /**
+ * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order.
+ *
+ * @param type the type to get the class hierarchy from
+ * @param interfacesBehavior switch indicating whether to include or exclude interfaces
+ * @return Iterable an Iterable over the class hierarchy of the given class
+ * @since 3.2
+ */
+ public static Iterable<Class<?>> hierarchy(final Class<?> type, final Interfaces interfacesBehavior) {
+ final Iterable<Class<?>> classes = () -> {
+ final MutableObject<Class<?>> next = new MutableObject<>(type);
+ return new Iterator<Class<?>>() {
+
+ @Override
+ public boolean hasNext() {
+ return next.getValue() != null;
+ }
+
+ @Override
+ public Class<?> next() {
+ final Class<?> result = next.getValue();
+ next.setValue(result.getSuperclass());
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ };
+ if (interfacesBehavior != Interfaces.INCLUDE) {
+ return classes;
+ }
+ return () -> {
+ final Set<Class<?>> seenInterfaces = new HashSet<>();
+ final Iterator<Class<?>> wrapped = classes.iterator();
+
+ return new Iterator<Class<?>>() {
+ Iterator<Class<?>> interfaces = Collections.emptyIterator();
+
+ @Override
+ public boolean hasNext() {
+ return interfaces.hasNext() || wrapped.hasNext();
+ }
+
+ @Override
+ public Class<?> next() {
+ if (interfaces.hasNext()) {
+ final Class<?> nextInterface = interfaces.next();
+ seenInterfaces.add(nextInterface);
+ return nextInterface;
+ }
+ final Class<?> nextSuperclass = wrapped.next();
+ final Set<Class<?>> currentInterfaces = new LinkedHashSet<>();
+ walkInterfaces(currentInterfaces, nextSuperclass);
+ interfaces = currentInterfaces.iterator();
+ return nextSuperclass;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private void walkInterfaces(final Set<Class<?>> addTo, final Class<?> c) {
+ for (final Class<?> iface : c.getInterfaces()) {
+ if (!seenInterfaces.contains(iface)) {
+ addTo.add(iface);
+ }
+ walkInterfaces(addTo, iface);
+ }
+ }
+
+ };
+ };
+ }
+
+ /**
+ * Checks if one {@link Class} can be assigned to a variable of another {@link Class}.
+ *
+ * <p>
+ * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
+ * primitive classes and {@code null}s.
+ * </p>
+ *
+ * <p>
+ * Primitive widenings allow an int to be assigned to a long, float or double. This method returns the correct result
+ * for these cases.
+ * </p>
+ *
+ * <p>
+ * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
+ * and the toClass is non-primitive.
+ * </p>
+ *
+ * <p>
+ * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
+ * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
+ * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
+ * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
+ * </p>
+ *
+ * <p>
+ * <strong>Since Lang 3.0,</strong> this method will default behavior for calculating assignability between primitive
+ * and wrapper types <em>corresponding to the running Java version</em>; i.e. autoboxing will be the default behavior in
+ * VMs running Java versions &gt; 1.5.
+ * </p>
+ *
+ * @param cls the Class to check, may be null
+ * @param toClass the Class to try to assign into, returns false if null
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(final Class<?> cls, final Class<?> toClass) {
+ return isAssignable(cls, toClass, true);
+ }
+
+ /**
+ * Checks if one {@link Class} can be assigned to a variable of another {@link Class}.
+ *
+ * <p>
+ * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
+ * primitive classes and {@code null}s.
+ * </p>
+ *
+ * <p>
+ * Primitive widenings allow an int to be assigned to a long, float or double. This method returns the correct result
+ * for these cases.
+ * </p>
+ *
+ * <p>
+ * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
+ * and the toClass is non-primitive.
+ * </p>
+ *
+ * <p>
+ * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
+ * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
+ * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
+ * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
+ * </p>
+ *
+ * @param cls the Class to check, may be null
+ * @param toClass the Class to try to assign into, returns false if null
+ * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?> cls, final Class<?> toClass, final boolean autoboxing) {
+ if (toClass == null) {
+ return false;
+ }
+ // have to check for null, as isAssignableFrom doesn't
+ if (cls == null) {
+ return !toClass.isPrimitive();
+ }
+ // autoboxing:
+ if (autoboxing) {
+ if (cls.isPrimitive() && !toClass.isPrimitive()) {
+ cls = primitiveToWrapper(cls);
+ if (cls == null) {
+ return false;
+ }
+ }
+ if (toClass.isPrimitive() && !cls.isPrimitive()) {
+ cls = wrapperToPrimitive(cls);
+ if (cls == null) {
+ return false;
+ }
+ }
+ }
+ if (cls.equals(toClass)) {
+ return true;
+ }
+ if (cls.isPrimitive()) {
+ if (!toClass.isPrimitive()) {
+ return false;
+ }
+ if (Integer.TYPE.equals(cls)) {
+ return Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
+ }
+ if (Long.TYPE.equals(cls)) {
+ return Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
+ }
+ if (Boolean.TYPE.equals(cls)) {
+ return false;
+ }
+ if (Double.TYPE.equals(cls)) {
+ return false;
+ }
+ if (Float.TYPE.equals(cls)) {
+ return Double.TYPE.equals(toClass);
+ }
+ if (Character.TYPE.equals(cls) || Short.TYPE.equals(cls)) {
+ return Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
+ }
+ if (Byte.TYPE.equals(cls)) {
+ return Short.TYPE.equals(toClass) || Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass)
+ || Double.TYPE.equals(toClass);
+ }
+ // should never get here
+ return false;
+ }
+ return toClass.isAssignableFrom(cls);
+ }
+
+ /**
+ * Checks if an array of Classes can be assigned to another array of Classes.
+ *
+ * <p>
+ * This method calls {@link #isAssignable(Class, Class) isAssignable} for each Class pair in the input arrays. It can be
+ * used to check if a set of arguments (the first parameter) are suitably compatible with a set of method parameter
+ * types (the second parameter).
+ * </p>
+ *
+ * <p>
+ * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
+ * primitive classes and {@code null}s.
+ * </p>
+ *
+ * <p>
+ * Primitive widenings allow an int to be assigned to a {@code long}, {@code float} or {@code double}. This method
+ * returns the correct result for these cases.
+ * </p>
+ *
+ * <p>
+ * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
+ * and the toClass is non-primitive.
+ * </p>
+ *
+ * <p>
+ * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
+ * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
+ * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
+ * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
+ * </p>
+ *
+ * <p>
+ * <strong>Since Lang 3.0,</strong> this method will default behavior for calculating assignability between primitive
+ * and wrapper types <em>corresponding to the running Java version</em>; i.e. autoboxing will be the default behavior in
+ * VMs running Java versions &gt; 1.5.
+ * </p>
+ *
+ * @param classArray the array of Classes to check, may be {@code null}
+ * @param toClassArray the array of Classes to try to assign into, may be {@code null}
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(final Class<?>[] classArray, final Class<?>... toClassArray) {
+ return isAssignable(classArray, toClassArray, true);
+ }
+
+ /**
+ * Checks if an array of Classes can be assigned to another array of Classes.
+ *
+ * <p>
+ * This method calls {@link #isAssignable(Class, Class) isAssignable} for each Class pair in the input arrays. It can be
+ * used to check if a set of arguments (the first parameter) are suitably compatible with a set of method parameter
+ * types (the second parameter).
+ * </p>
+ *
+ * <p>
+ * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
+ * primitive classes and {@code null}s.
+ * </p>
+ *
+ * <p>
+ * Primitive widenings allow an int to be assigned to a {@code long}, {@code float} or {@code double}. This method
+ * returns the correct result for these cases.
+ * </p>
+ *
+ * <p>
+ * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
+ * and the toClass is non-primitive.
+ * </p>
+ *
+ * <p>
+ * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
+ * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
+ * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
+ * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
+ * </p>
+ *
+ * @param classArray the array of Classes to check, may be {@code null}
+ * @param toClassArray the array of Classes to try to assign into, may be {@code null}
+ * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
+ * @return {@code true} if assignment possible
+ */
+ public static boolean isAssignable(Class<?>[] classArray, Class<?>[] toClassArray, final boolean autoboxing) {
+ if (!ArrayUtils.isSameLength(classArray, toClassArray)) {
+ return false;
+ }
+ if (classArray == null) {
+ classArray = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ if (toClassArray == null) {
+ toClassArray = ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ for (int i = 0; i < classArray.length; i++) {
+ if (!isAssignable(classArray[i], toClassArray[i], autoboxing)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Is the specified class an inner class or static nested class.
+ *
+ * @param cls the class to check, may be null
+ * @return {@code true} if the class is an inner or static nested class, false if not or {@code null}
+ */
+ public static boolean isInnerClass(final Class<?> cls) {
+ 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}).
+ *
+ * @param type The class to query or null.
+ * @return true if 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}).
+ * @since 3.1
+ */
+ public static boolean isPrimitiveOrWrapper(final Class<?> type) {
+ if (type == null) {
+ return false;
+ }
+ 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}).
+ *
+ * @param type The class to query or null.
+ * @return true if the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character},
+ * {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}).
+ * @since 3.1
+ */
+ public static boolean isPrimitiveWrapper(final Class<?> type) {
+ return wrapperPrimitiveMap.containsKey(type);
+ }
+
+ /**
+ * Converts the specified array of primitive Class objects to an array of its corresponding wrapper Class objects.
+ *
+ * @param classes the class array to convert, may be null or empty
+ * @return an array which contains for each given class, the wrapper class or the original class if class is not a
+ * primitive. {@code null} if null input. Empty array if an empty array passed in.
+ * @since 2.1
+ */
+ public static Class<?>[] primitivesToWrappers(final Class<?>... classes) {
+ if (classes == null) {
+ return null;
+ }
+
+ if (classes.length == 0) {
+ return classes;
+ }
+
+ final Class<?>[] convertedClasses = new Class[classes.length];
+ Arrays.setAll(convertedClasses, i -> primitiveToWrapper(classes[i]));
+ return convertedClasses;
+ }
+
+ /**
+ * Converts the specified primitive Class object to its corresponding wrapper Class object.
+ *
+ * <p>
+ * NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
+ * </p>
+ *
+ * @param cls the class to convert, may be null
+ * @return the wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive. {@code null} if null
+ * input.
+ * @since 2.1
+ */
+ public static Class<?> primitiveToWrapper(final Class<?> cls) {
+ Class<?> convertedClass = cls;
+ if (cls != null && cls.isPrimitive()) {
+ convertedClass = primitiveWrapperMap.get(cls);
+ }
+ return convertedClass;
+ }
+
+ /**
+ * Converts a class name to a JLS style class name.
+ *
+ * @param className the class name
+ * @return the converted name
+ * @throws NullPointerException if the className is null
+ */
+ private static String toCanonicalName(final String className) {
+ String canonicalName = StringUtils.deleteWhitespace(className);
+ Objects.requireNonNull(canonicalName, "className");
+ if (canonicalName.endsWith("[]")) {
+ final StringBuilder classNameBuffer = new StringBuilder();
+ while (canonicalName.endsWith("[]")) {
+ canonicalName = canonicalName.substring(0, canonicalName.length() - 2);
+ classNameBuffer.append("[");
+ }
+ final String abbreviation = abbreviationMap.get(canonicalName);
+ if (abbreviation != null) {
+ classNameBuffer.append(abbreviation);
+ } else {
+ classNameBuffer.append("L").append(canonicalName).append(";");
+ }
+ canonicalName = classNameBuffer.toString();
+ }
+ return canonicalName;
+ }
+
+ /**
+ * Converts an array of {@link Object} in to an array of {@link Class} objects. If any of these objects is null, a null
+ * element will be inserted into the array.
+ *
+ * <p>
+ * This method returns {@code null} for a {@code null} input array.
+ * </p>
+ *
+ * @param array an {@link Object} array
+ * @return a {@link Class} array, {@code null} if null array input
+ * @since 2.4
+ */
+ public static Class<?>[] toClass(final Object... array) {
+ if (array == null) {
+ return null;
+ }
+ if (array.length == 0) {
+ return ArrayUtils.EMPTY_CLASS_ARRAY;
+ }
+ final Class<?>[] classes = new Class[array.length];
+ Arrays.setAll(classes, i -> array[i] == null ? null : array[i].getClass());
+ return classes;
+ }
+
+ /**
+ * Decides if the part that was just copied to its destination location in the work array can be kept as it was copied
+ * or must be abbreviated. It must be kept when the part is the last one, which is the simple name of the class. In this
+ * case the {@code source} index, from where the characters are copied points one position after the last character,
+ * a.k.a. {@code source ==
+ * originalLength}
+ *
+ * <p>
+ * If the part is not the last one then it can be kept unabridged if the number of the characters copied so far plus the
+ * character that are to be copied is less than or equal to the desired length.
+ * </p>
+ *
+ * @param runAheadTarget the target index (where the characters were copied to) pointing after the last character copied
+ * when the current part was copied
+ * @param source the source index (where the characters were copied from) pointing after the last character copied when
+ * the current part was copied
+ * @param originalLength the original length of the class full name, which is abbreviated
+ * @param desiredLength the desired length of the abbreviated class name
+ * @return {@code true} if it can be kept in its original length {@code false} if the current part has to be abbreviated
+ * and
+ */
+ private static boolean useFull(final int runAheadTarget, final int source, final int originalLength, final int desiredLength) {
+ return source >= originalLength || runAheadTarget + originalLength - source <= desiredLength;
+ }
+
+ /**
+ * Converts the specified array of wrapper Class objects to an array of its corresponding primitive Class objects.
+ *
+ * <p>
+ * This method invokes {@code wrapperToPrimitive()} for each element of the passed in array.
+ * </p>
+ *
+ * @param classes the class array to convert, may be null or empty
+ * @return an array which contains for each given class, the primitive class or <b>null</b> if the original class is not
+ * a wrapper class. {@code null} if null input. Empty array if an empty array passed in.
+ * @see #wrapperToPrimitive(Class)
+ * @since 2.4
+ */
+ public static Class<?>[] wrappersToPrimitives(final Class<?>... classes) {
+ if (classes == null) {
+ return null;
+ }
+
+ if (classes.length == 0) {
+ return classes;
+ }
+
+ final Class<?>[] convertedClasses = new Class[classes.length];
+ Arrays.setAll(convertedClasses, i -> wrapperToPrimitive(classes[i]));
+ return convertedClasses;
+ }
+
+ /**
+ * Converts the specified wrapper class to its corresponding primitive class.
+ *
+ * <p>
+ * This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is a wrapper class for a
+ * primitive type, this primitive type will be returned (e.g. {@code Integer.TYPE} for {@code Integer.class}). For other
+ * classes, or if the parameter is <b>null</b>, the return value is <b>null</b>.
+ * </p>
+ *
+ * @param cls the class to convert, may be <b>null</b>
+ * @return the corresponding primitive type if {@code cls} is a wrapper class, <b>null</b> otherwise
+ * @see #primitiveToWrapper(Class)
+ * @since 2.4
+ */
+ public static Class<?> wrapperToPrimitive(final Class<?> cls) {
+ return wrapperPrimitiveMap.get(cls);
+ }
+
+ /**
+ * ClassUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code ClassUtils.getShortClassName(cls)}.
+ *
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public ClassUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/Conversion.java b/src/main/java/org/apache/commons/lang3/Conversion.java
new file mode 100644
index 000000000..a4ef5b778
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Conversion.java
@@ -0,0 +1,1492 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.UUID;
+
+
+/**
+ * Static methods to convert a type into another, with endianness and bit ordering awareness.
+ *
+ * <p>
+ * The methods names follow a naming rule:<br>
+ * {@code <source type>[source endianness][source bit ordering]To<destination type>[destination endianness][destination bit ordering]}
+ * </p>
+ * <p>
+ * Source/destination type fields is one of the following:
+ * </p>
+ * <ul>
+ * <li>binary: an array of booleans</li>
+ * <li>byte or byteArray</li>
+ * <li>int or intArray</li>
+ * <li>long or longArray</li>
+ * <li>hex: a String containing hexadecimal digits (lowercase in destination)</li>
+ * <li>hexDigit: a Char containing a hexadecimal digit (lowercase in destination)</li>
+ * <li>uuid</li>
+ * </ul>
+ * <p>
+ * Endianness field: little endian is the default, in this case the field is absent. In case of
+ * big endian, the field is "Be".<br> Bit ordering: Lsb0 is the default, in this case the field
+ * is absent. In case of Msb0, the field is "Msb0".
+ * </p>
+ * <p>
+ * Example: intBeMsb0ToHex convert an int with big endian byte order and Msb0 bit order into its
+ * hexadecimal string representation
+ * </p>
+ * <p>
+ * Most of the methods provide only default encoding for destination, this limits the number of
+ * ways to do one thing. Unless you are dealing with data from/to outside of the JVM platform,
+ * you should not need to use "Be" and "Msb0" methods.
+ * </p>
+ * <p>
+ * Development status: work on going, only a part of the little endian, Lsb0 methods implemented
+ * so far.
+ * </p>
+ *
+ * @since 3.2
+ */
+
+public class Conversion {
+
+ private static final boolean[] TTTT = {true, true, true, true};
+ private static final boolean[] FTTT = {false, true, true, true};
+ private static final boolean[] TFTT = {true, false, true, true};
+ private static final boolean[] FFTT = {false, false, true, true};
+ private static final boolean[] TTFT = {true, true, false, true};
+ private static final boolean[] FTFT = {false, true, false, true};
+ private static final boolean[] TFFT = {true, false, false, true};
+ private static final boolean[] FFFT = {false, false, false, true};
+ private static final boolean[] TTTF = {true, true, true, false};
+ private static final boolean[] FTTF = {false, true, true, false};
+ private static final boolean[] TFTF = {true, false, true, false};
+ private static final boolean[] FFTF = {false, false, true, false};
+ private static final boolean[] TTFF = {true, true, false, false};
+ private static final boolean[] FTFF = {false, true, false, false};
+ private static final boolean[] TFFF = {true, false, false, false};
+ private static final boolean[] FFFF = {false, false, false, false};
+
+ /**
+ * Converts a hexadecimal digit into an int using the default (Lsb0) bit ordering.
+ *
+ * <p>
+ * '1' is converted to 1
+ * </p>
+ *
+ * @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 a hexadecimal digit into an int using the Msb0 bit ordering.
+ *
+ * <p>
+ * '1' is converted to 8
+ * </p>
+ *
+ * @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.
+ *
+ * <p>
+ * '1' is converted as follow: (1, 0, 0, 0)
+ * </p>
+ *
+ * @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 binary (represented as boolean array) using the Msb0
+ * bit ordering.
+ *
+ * <p>
+ * '1' is converted as follow: (0, 0, 0, 1)
+ * </p>
+ *
+ * @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");
+ }
+ }
+
+ /**
+ * Converts binary (represented as boolean array) to a hexadecimal digit using the default
+ * (Lsb0) bit ordering.
+ *
+ * <p>
+ * (1, 0, 0, 0) is converted as follow: '1'
+ * </p>
+ *
+ * @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 char binaryToHexDigit(final boolean[] src) {
+ return binaryToHexDigit(src, 0);
+ }
+
+ /**
+ * Converts binary (represented as boolean array) to a hexadecimal digit using the default
+ * (Lsb0) bit ordering.
+ *
+ * <p>
+ * (1, 0, 0, 0) is converted as follow: '1'
+ * </p>
+ *
+ * @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}
+ */
+ public static char binaryToHexDigit(final boolean[] src, final int srcPos) {
+ if (src.length == 0) {
+ throw new IllegalArgumentException("Cannot convert an empty array.");
+ }
+ if (src.length > srcPos + 3 && src[srcPos + 3]) {
+ if (src[srcPos + 2]) {
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'f' : 'e';
+ }
+ return src[srcPos] ? 'd' : 'c';
+ }
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'b' : 'a';
+ }
+ return src[srcPos] ? '9' : '8';
+ }
+ if (src.length > srcPos + 2 && src[srcPos + 2]) {
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? '7' : '6';
+ }
+ return src[srcPos] ? '5' : '4';
+ }
+ if (src.length > srcPos + 1 && src[srcPos + 1]) {
+ return src[srcPos] ? '3' : '2';
+ }
+ return src[srcPos] ? '1' : '0';
+ }
+
+ /**
+ * Converts binary (represented as boolean array) to a hexadecimal digit using the Msb0 bit
+ * ordering.
+ *
+ * <p>
+ * (1, 0, 0, 0) is converted as follow: '8'
+ * </p>
+ *
+ * @param src the binary to convert
+ * @return a hexadecimal digit representing the selected bits
+ * @throws IllegalArgumentException if {@code src} is empty, {@code src.length < 4} or
+ * {@code src.length > 8}
+ * @throws NullPointerException if {@code src} is {@code null}
+ */
+ public static char binaryToHexDigitMsb0_4bits(final boolean[] src) {
+ return binaryToHexDigitMsb0_4bits(src, 0);
+ }
+
+ /**
+ * Converts binary (represented as boolean array) to a hexadecimal digit using the Msb0 bit
+ * ordering.
+ *
+ * <p>
+ * (1, 0, 0, 0) is converted as follow: '8' (1, 0, 0, 1, 1, 0, 1, 0) with srcPos = 3 is converted
+ * to 'D'
+ * </p>
+ *
+ * @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, {@code src.length > 8} or
+ * {@code src.length - srcPos < 4}
+ * @throws NullPointerException if {@code src} is {@code null}
+ */
+ public static char binaryToHexDigitMsb0_4bits(final boolean[] src, final int srcPos) {
+ if (src.length > 8) {
+ throw new IllegalArgumentException("src.length>8: src.length=" + src.length);
+ }
+ if (src.length - srcPos < 4) {
+ throw new IllegalArgumentException("src.length-srcPos<4: src.length=" + src.length + ", srcPos=" + srcPos);
+ }
+ if (src[srcPos + 3]) {
+ if (src[srcPos + 2]) {
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'f' : '7';
+ }
+ return src[srcPos] ? 'b' : '3';
+ }
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'd' : '5';
+ }
+ return src[srcPos] ? '9' : '1';
+ }
+ if (src[srcPos + 2]) {
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'e' : '6';
+ }
+ return src[srcPos] ? 'a' : '2';
+ }
+ if (src[srcPos + 1]) {
+ return src[srcPos] ? 'c' : '4';
+ }
+ return src[srcPos] ? '8' : '0';
+ }
+
+ /**
+ * Converts the first 4 bits of a binary (represented as boolean array) in big endian Msb0
+ * bit ordering to a hexadecimal digit.
+ *
+ * <p>
+ * (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'
+ * </p>
+ *
+ * @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 char binaryBeMsb0ToHexDigit(final boolean[] src) {
+ return binaryBeMsb0ToHexDigit(src, 0);
+ }
+
+ /**
+ * Converts a binary (represented as boolean array) in big endian Msb0 bit ordering to a
+ * hexadecimal digit.
+ *
+ * <p>
+ * (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'
+ * </p>
+ *
+ * @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 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 the 4 lsb of an int to a hexadecimal digit.
+ *
+ * <p>
+ * 0 returns '0'
+ * </p>
+ * <p>
+ * 1 returns '1'
+ * </p>
+ * <p>
+ * 10 returns 'A' and so on...
+ * </p>
+ *
+ * @param nibble the 4 bits to convert
+ * @return a hexadecimal digit representing the 4 lsb of {@code nibble}
+ * @throws IllegalArgumentException if {@code nibble < 0} or {@code nibble > 15}
+ */
+ public static char intToHexDigit(final int nibble) {
+ final char c = Character.forDigit(nibble, 16);
+ if (c == Character.MIN_VALUE) {
+ throw new IllegalArgumentException("nibble value not between 0 and 15: " + nibble);
+ }
+ return c;
+ }
+
+ /**
+ * Converts the 4 lsb of an int to a hexadecimal digit encoded using the Msb0 bit ordering.
+ *
+ * <p>
+ * 0 returns '0'
+ * </p>
+ * <p>
+ * 1 returns '8'
+ * </p>
+ * <p>
+ * 10 returns '5' and so on...
+ * </p>
+ *
+ * @param nibble the 4 bits to convert
+ * @return a hexadecimal digit representing the 4 lsb of {@code nibble}
+ * @throws IllegalArgumentException if {@code nibble < 0} or {@code nibble > 15}
+ */
+ public static char intToHexDigitMsb0(final int nibble) {
+ switch (nibble) {
+ case 0x0:
+ return '0';
+ case 0x1:
+ return '8';
+ case 0x2:
+ return '4';
+ case 0x3:
+ return 'c';
+ case 0x4:
+ return '2';
+ case 0x5:
+ return 'a';
+ case 0x6:
+ return '6';
+ case 0x7:
+ return 'e';
+ case 0x8:
+ return '1';
+ case 0x9:
+ return '9';
+ case 0xA:
+ return '5';
+ case 0xB:
+ return 'd';
+ case 0xC:
+ return '3';
+ case 0xD:
+ return 'b';
+ case 0xE:
+ return '7';
+ case 0xF:
+ return 'f';
+ default:
+ throw new IllegalArgumentException("nibble value not between 0 and 15: " + nibble);
+ }
+ }
+
+ /**
+ * 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
+ * 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 an array of short into an int 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}
+ */
+ 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 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 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
+ * 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 an array of Char into a long using the default (little endian, Lsb0) byte and
+ * bit ordering.
+ *
+ * @param src the hex 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 hex 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 hex 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 hex 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;
+ }
+
+ /**
+ * Converts a long into an array of int 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 nInts the number of ints 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} and {@code nInts > 0}
+ * @throws IllegalArgumentException if {@code (nInts-1)*32+srcPos >= 64}
+ * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nInts > dst.length}
+ */
+ public static int[] longToIntArray(final long src, final int srcPos, final int[] dst, final int dstPos,
+ final int nInts) {
+ if (0 == nInts) {
+ return dst;
+ }
+ if ((nInts - 1) * 32 + srcPos >= 64) {
+ throw new IllegalArgumentException("(nInts-1)*32+srcPos is greater or equal to than 64");
+ }
+ for (int i = 0; i < nInts; i++) {
+ final int shift = i * 32 + srcPos;
+ dst[dstPos + i] = (int) (0xffffffff & (src >> shift));
+ }
+ return dst;
+ }
+
+ /**
+ * Converts a long into an array of short 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 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 >= 64}
+ * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nShorts > dst.length}
+ */
+ public static short[] longToShortArray(final long src, final int srcPos, final short[] dst, final int dstPos,
+ final int nShorts) {
+ if (0 == nShorts) {
+ return dst;
+ }
+ if ((nShorts - 1) * 16 + srcPos >= 64) {
+ throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greater or equal to than 64");
+ }
+ for (int i = 0; i < nShorts; i++) {
+ final int shift = i * 16 + srcPos;
+ dst[dstPos + i] = (short) (0xffff & (src >> shift));
+ }
+ return dst;
+ }
+
+ /**
+ * Converts an int into an array of short 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 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 short[] intToShortArray(final int src, final int srcPos, final short[] dst, final int dstPos,
+ final int nShorts) {
+ if (0 == nShorts) {
+ return dst;
+ }
+ if ((nShorts - 1) * 16 + srcPos >= 32) {
+ throw new IllegalArgumentException("(nShorts-1)*16+srcPos is greater or equal to than 32");
+ }
+ 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));
+ }
+ return dst;
+ }
+
+ /**
+ * Converts a short into an array of byte 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 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 >= 16}
+ * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length}
+ */
+ public static byte[] shortToByteArray(final short src, final int srcPos, final byte[] dst, final int dstPos,
+ final int nBytes) {
+ if (0 == nBytes) {
+ return dst;
+ }
+ if ((nBytes - 1) * 8 + srcPos >= 16) {
+ throw new IllegalArgumentException("(nBytes-1)*8+srcPos is greater or equal to than 16");
+ }
+ for (int i = 0; i < nBytes; i++) {
+ final int shift = i * 8 + srcPos;
+ dst[dstPos + i] = (byte) (0xff & (src >> shift));
+ }
+ 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.
+ *
+ * @param src the short 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 >= 16}
+ * @throws StringIndexOutOfBoundsException if {@code dst.init.length() < dstPos}
+ */
+ public static String shortToHex(final short src, final int srcPos, final String dstInit, final int dstPos,
+ final int nHexs) {
+ if (0 == nHexs) {
+ return dstInit;
+ }
+ if ((nHexs - 1) * 4 + srcPos >= 16) {
+ throw new IllegalArgumentException("(nHexs-1)*4+srcPos is greater or equal to than 16");
+ }
+ 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 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.
+ *
+ * @param src the UUID to convert
+ * @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 > 16}
+ * @throws ArrayIndexOutOfBoundsException if {@code dstPos + nBytes > dst.length}
+ */
+ public static byte[] uuidToByteArray(final UUID src, final byte[] dst, final int dstPos, final int nBytes) {
+ if (0 == nBytes) {
+ return dst;
+ }
+ if (nBytes > 16) {
+ throw new IllegalArgumentException("nBytes is greater than 16");
+ }
+ longToByteArray(src.getMostSignificantBits(), 0, dst, dstPos, Math.min(nBytes, 8));
+ if (nBytes >= 8) {
+ longToByteArray(src.getLeastSignificantBits(), 0, dst, dstPos + 8, nBytes - 8);
+ }
+ 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/DoubleRange.java b/src/main/java/org/apache/commons/lang3/DoubleRange.java
new file mode 100644
index 000000000..7816b8161
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/DoubleRange.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+/**
+ * Specializes {@link NumberRange} for {@link Double}s.
+ *
+ * <p>
+ * This class is not designed to interoperate with other NumberRanges
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public final class DoubleRange extends NumberRange<Double> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ */
+ public static DoubleRange of(final double fromInclusive, final double toInclusive) {
+ return of(Double.valueOf(fromInclusive), Double.valueOf(toInclusive));
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ * @throws IllegalArgumentException if either element is null.
+ */
+ public static DoubleRange of(final Double fromInclusive, final Double toInclusive) {
+ return new DoubleRange(fromInclusive, toInclusive);
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param number1 the first element, not null
+ * @param number2 the second element, not null
+ * @throws NullPointerException when element1 is null.
+ * @throws NullPointerException when element2 is null.
+ */
+ private DoubleRange(final Double number1, final Double number2) {
+ super(number1, number2, null);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/EnumUtils.java b/src/main/java/org/apache/commons/lang3/EnumUtils.java
new file mode 100644
index 000000000..f5e0055e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/EnumUtils.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Utility library to provide helper methods for Java enums.
+ *
+ * <p>#ThreadSafe#</p>
+ *
+ * @since 3.0
+ */
+public class EnumUtils {
+
+ private static final String CANNOT_STORE_S_S_VALUES_IN_S_BITS = "Cannot store %s %s values in %s bits";
+ private static final String ENUM_CLASS_MUST_BE_DEFINED = "EnumClass must be defined.";
+ private static final String NULL_ELEMENTS_NOT_PERMITTED = "null elements not permitted";
+ private static final String S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE = "%s does not seem to be an Enum type";
+
+ /**
+ * Validate {@code enumClass}.
+ * @param <E> the type of the enumeration
+ * @param enumClass to check
+ * @return {@code enumClass}
+ * @throws NullPointerException if {@code enumClass} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class
+ * @since 3.2
+ */
+ private static <E extends Enum<E>> Class<E> asEnum(final Class<E> enumClass) {
+ Objects.requireNonNull(enumClass, ENUM_CLASS_MUST_BE_DEFINED);
+ Validate.isTrue(enumClass.isEnum(), S_DOES_NOT_SEEM_TO_BE_AN_ENUM_TYPE, enumClass);
+ return enumClass;
+ }
+
+ /**
+ * Validate that {@code enumClass} is compatible with representation in a {@code long}.
+ * @param <E> the type of the enumeration
+ * @param enumClass to check
+ * @return {@code enumClass}
+ * @throws NullPointerException if {@code enumClass} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
+ * @since 3.0.1
+ */
+ private static <E extends Enum<E>> Class<E> checkBitVectorable(final Class<E> enumClass) {
+ final E[] constants = asEnum(enumClass).getEnumConstants();
+ Validate.isTrue(constants.length <= Long.SIZE, CANNOT_STORE_S_S_VALUES_IN_S_BITS,
+ Integer.valueOf(constants.length), enumClass.getSimpleName(), Integer.valueOf(Long.SIZE));
+
+ return enumClass;
+ }
+
+ /**
+ * Creates a long bit vector representation of the given array of Enum values.
+ *
+ * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
+ *
+ * <p>Do not use this method if you have more than 64 values in your Enum, as this
+ * would create a value greater than a long can hold.</p>
+ *
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param values the values we want to convert, not {@code null}
+ * @param <E> the type of the enumeration
+ * @return a long whose value provides a binary representation of the given set of enum values.
+ * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
+ * @since 3.0.1
+ * @see #generateBitVectors(Class, Iterable)
+ */
+ @SafeVarargs
+ public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final E... values) {
+ Validate.noNullElements(values);
+ return generateBitVector(enumClass, Arrays.asList(values));
+ }
+
+ /**
+ * Creates a long bit vector representation of the given subset of an Enum.
+ *
+ * <p>This generates a value that is usable by {@link EnumUtils#processBitVector}.</p>
+ *
+ * <p>Do not use this method if you have more than 64 values in your Enum, as this
+ * would create a value greater than a long can hold.</p>
+ *
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param values the values we want to convert, not {@code null}, neither containing {@code null}
+ * @param <E> the type of the enumeration
+ * @return a long whose value provides a binary representation of the given set of enum values.
+ * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values,
+ * or if any {@code values} {@code null}
+ * @since 3.0.1
+ * @see #generateBitVectors(Class, Iterable)
+ */
+ public static <E extends Enum<E>> long generateBitVector(final Class<E> enumClass, final Iterable<? extends E> values) {
+ checkBitVectorable(enumClass);
+ Objects.requireNonNull(values, "values");
+ long total = 0;
+ for (final E constant : values) {
+ Objects.requireNonNull(constant, NULL_ELEMENTS_NOT_PERMITTED);
+ total |= 1L << constant.ordinal();
+ }
+ return total;
+ }
+
+ /**
+ * Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.
+ *
+ * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
+ *
+ * <p>Use this method if you have more than 64 values in your Enum.</p>
+ *
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param values the values we want to convert, not {@code null}, neither containing {@code null}
+ * @param <E> the type of the enumeration
+ * @return a long[] whose values provide a binary representation of the given set of enum values
+ * with the least significant digits rightmost.
+ * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
+ * @since 3.2
+ */
+ @SafeVarargs
+ public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final E... values) {
+ asEnum(enumClass);
+ Validate.noNullElements(values);
+ final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
+ Collections.addAll(condensed, values);
+ final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
+ for (final E value : condensed) {
+ result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
+ }
+ ArrayUtils.reverse(result);
+ return result;
+ }
+
+ /**
+ * Creates a bit vector representation of the given subset of an Enum using as many {@code long}s as needed.
+ *
+ * <p>This generates a value that is usable by {@link EnumUtils#processBitVectors}.</p>
+ *
+ * <p>Use this method if you have more than 64 values in your Enum.</p>
+ *
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param values the values we want to convert, not {@code null}, neither containing {@code null}
+ * @param <E> the type of the enumeration
+ * @return a long[] whose values provide a binary representation of the given set of enum values
+ * with the least significant digits rightmost.
+ * @throws NullPointerException if {@code enumClass} or {@code values} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class, or if any {@code values} {@code null}
+ * @since 3.2
+ */
+ public static <E extends Enum<E>> long[] generateBitVectors(final Class<E> enumClass, final Iterable<? extends E> values) {
+ asEnum(enumClass);
+ Objects.requireNonNull(values, "values");
+ final EnumSet<E> condensed = EnumSet.noneOf(enumClass);
+ values.forEach(constant -> condensed.add(Objects.requireNonNull(constant, NULL_ELEMENTS_NOT_PERMITTED)));
+ final long[] result = new long[(enumClass.getEnumConstants().length - 1) / Long.SIZE + 1];
+ for (final E value : condensed) {
+ result[value.ordinal() / Long.SIZE] |= 1L << (value.ordinal() % Long.SIZE);
+ }
+ ArrayUtils.reverse(result);
+ return result;
+ }
+
+ /**
+ * Gets the enum for the class, returning {@code null} if not found.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
+ * for an invalid enum name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns null
+ * @return the enum, null if not found
+ */
+ public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName) {
+ return getEnum(enumClass, enumName, null);
+ }
+
+ /**
+ * Gets the enum for the class, returning {@code defaultEnum} if not found.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
+ * for an invalid enum name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns default enum
+ * @param defaultEnum the default enum
+ * @return the enum, default enum if not found
+ * @since 3.10
+ */
+ public static <E extends Enum<E>> E getEnum(final Class<E> enumClass, final String enumName, final E defaultEnum) {
+ if (enumName == null) {
+ return defaultEnum;
+ }
+ try {
+ return Enum.valueOf(enumClass, enumName);
+ } catch (final IllegalArgumentException ex) {
+ return defaultEnum;
+ }
+ }
+
+ /**
+ * Gets the enum for the class, returning {@code null} if not found.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
+ * for an invalid enum name and performs case insensitive matching of the name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns null
+ * @return the enum, null if not found
+ * @since 3.8
+ */
+ public static <E extends Enum<E>> E getEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
+ return getEnumIgnoreCase(enumClass, enumName, null);
+ }
+
+ /**
+ * Gets the enum for the class, returning {@code defaultEnum} if not found.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
+ * for an invalid enum name and performs case insensitive matching of the name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns default enum
+ * @param defaultEnum the default enum
+ * @return the enum, default enum if not found
+ * @since 3.10
+ */
+ public static <E extends Enum<E>> E getEnumIgnoreCase(final Class<E> enumClass, final String enumName,
+ final E defaultEnum) {
+ return getFirstEnumIgnoreCase(enumClass, enumName, Enum::name, defaultEnum);
+ }
+
+ /**
+ * Gets the {@link List} of enums.
+ *
+ * <p>This method is useful when you need a list of enums rather than an array.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @return the modifiable list of enums, never null
+ */
+ public static <E extends Enum<E>> List<E> getEnumList(final Class<E> enumClass) {
+ return new ArrayList<>(Arrays.asList(enumClass.getEnumConstants()));
+ }
+
+ /**
+ * Gets the {@link Map} of enums by name.
+ *
+ * <p>This method is useful when you need a map of enums by name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @return the modifiable map of enum names to enums, never null
+ */
+ public static <E extends Enum<E>> Map<String, E> getEnumMap(final Class<E> enumClass) {
+ return getEnumMap(enumClass, E::name);
+ }
+
+ /**
+ * Gets the {@link Map} of enums by name.
+ *
+ * <p>
+ * This method is useful when you need a map of enums by name.
+ * </p>
+ *
+ * @param <E> the type of enumeration
+ * @param <K> the type of the map key
+ * @param enumClass the class of the enum to query, not null
+ * @param keyFunction the function to query for the key, not null
+ * @return the modifiable map of enums, never null
+ * @since 3.13.0
+ */
+ public static <E extends Enum<E>, K> Map<K, E> getEnumMap(final Class<E> enumClass, final Function<E, K> keyFunction) {
+ return Stream.of(enumClass.getEnumConstants()).collect(Collectors.toMap(keyFunction::apply, Function.identity()));
+ }
+
+ /**
+ * Gets the enum for the class in a system property, returning {@code defaultEnum} if not found.
+ *
+ * <p>
+ * This method differs from {@link Enum#valueOf} in that it does not throw an exception for an invalid enum name.
+ * </p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param propName the system property key for the enum name, null returns default enum
+ * @param defaultEnum the default enum
+ * @return the enum, default enum if not found
+ * @since 3.13.0
+ */
+ public static <E extends Enum<E>> E getEnumSystemProperty(final Class<E> enumClass, final String propName,
+ final E defaultEnum) {
+ return enumClass == null || propName == null ? defaultEnum
+ : getEnum(enumClass, System.getProperty(propName), defaultEnum);
+ }
+
+ /**
+ * Gets the enum for the class, returning {@code defaultEnum} if not found.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that it does not throw an exception
+ * for an invalid enum name and performs case insensitive matching of the name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns default enum
+ * @param stringFunction the function that gets the string for an enum for comparison to {@code enumName}.
+ * @param defaultEnum the default enum
+ * @return the enum, default enum if not found
+ * @since 3.13.0
+ */
+ public static <E extends Enum<E>> E getFirstEnumIgnoreCase(final Class<E> enumClass, final String enumName, final Function<E, String> stringFunction,
+ final E defaultEnum) {
+ if (enumName == null || !enumClass.isEnum()) {
+ return defaultEnum;
+ }
+ return Stream.of(enumClass.getEnumConstants()).filter(e -> enumName.equalsIgnoreCase(stringFunction.apply(e))).findFirst().orElse(defaultEnum);
+ }
+
+ /**
+ * Checks if the specified name is a valid enum for the class.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that checks if the name is
+ * a valid enum without needing to catch the exception.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns false
+ * @return true if the enum name is valid, otherwise false
+ */
+ public static <E extends Enum<E>> boolean isValidEnum(final Class<E> enumClass, final String enumName) {
+ return getEnum(enumClass, enumName) != null;
+ }
+
+ /**
+ * Checks if the specified name is a valid enum for the class.
+ *
+ * <p>This method differs from {@link Enum#valueOf} in that checks if the name is
+ * a valid enum without needing to catch the exception
+ * and performs case insensitive matching of the name.</p>
+ *
+ * @param <E> the type of the enumeration
+ * @param enumClass the class of the enum to query, not null
+ * @param enumName the enum name, null returns false
+ * @return true if the enum name is valid, otherwise false
+ * @since 3.8
+ */
+ public static <E extends Enum<E>> boolean isValidEnumIgnoreCase(final Class<E> enumClass, final String enumName) {
+ return getEnumIgnoreCase(enumClass, enumName) != null;
+ }
+
+ /**
+ * Convert a long value created by {@link EnumUtils#generateBitVector} into the set of
+ * enum values that it represents.
+ *
+ * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param value the long value representation of a set of enum values
+ * @param <E> the type of the enumeration
+ * @return a set of enum values
+ * @throws NullPointerException if {@code enumClass} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class or has more than 64 values
+ * @since 3.0.1
+ */
+ public static <E extends Enum<E>> EnumSet<E> processBitVector(final Class<E> enumClass, final long value) {
+ checkBitVectorable(enumClass).getEnumConstants();
+ return processBitVectors(enumClass, value);
+ }
+
+ /**
+ * Convert a {@code long[]} created by {@link EnumUtils#generateBitVectors} into the set of
+ * enum values that it represents.
+ *
+ * <p>If you store this value, beware any changes to the enum that would affect ordinal values.</p>
+ * @param enumClass the class of the enum we are working with, not {@code null}
+ * @param values the long[] bearing the representation of a set of enum values, the least significant digits rightmost, not {@code null}
+ * @param <E> the type of the enumeration
+ * @return a set of enum values
+ * @throws NullPointerException if {@code enumClass} is {@code null}
+ * @throws IllegalArgumentException if {@code enumClass} is not an enum class
+ * @since 3.2
+ */
+ public static <E extends Enum<E>> EnumSet<E> processBitVectors(final Class<E> enumClass, final long... values) {
+ final EnumSet<E> results = EnumSet.noneOf(asEnum(enumClass));
+ final long[] lvalues = ArrayUtils.clone(Objects.requireNonNull(values, "values"));
+ ArrayUtils.reverse(lvalues);
+ for (final E constant : enumClass.getEnumConstants()) {
+ final int block = constant.ordinal() / Long.SIZE;
+ if (block < lvalues.length && (lvalues[block] & 1L << (constant.ordinal() % Long.SIZE)) != 0) {
+ results.add(constant);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * This constructor is public to permit tools that require a JavaBean
+ * instance to operate.
+ */
+ public EnumUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/Functions.java b/src/main/java/org/apache/commons/lang3/Functions.java
new file mode 100644
index 000000000..e5e4e106c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Functions.java
@@ -0,0 +1,662 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.Streams.FailableStream;
+import org.apache.commons.lang3.function.Failable;
+import org.apache.commons.lang3.function.FailableBooleanSupplier;
+
+/**
+ * This class provides utility functions, and classes for working with the {@code java.util.function} package, or more
+ * generally, with Java 8 lambdas. More specifically, it attempts to address the fact that lambdas are supposed not to
+ * throw Exceptions, at least not checked Exceptions, AKA instances of {@link Exception}. This enforces the use of
+ * constructs like:
+ *
+ * <pre>
+ * {@code
+ * Consumer<java.lang.reflect.Method> consumer = m -> {
+ * try {
+ * m.invoke(o, args);
+ * } catch (Throwable t) {
+ * throw Functions.rethrow(t);
+ * }
+ * };
+ * }</pre>
+ *
+ * <p>
+ * By replacing a {@link java.util.function.Consumer Consumer&lt;O&gt;} with a {@link FailableConsumer
+ * FailableConsumer&lt;O,? extends Throwable&gt;}, this can be written like follows:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * Functions.accept((m) -> m.invoke(o,args));
+ * }</pre>
+ *
+ * <p>
+ * Obviously, the second version is much more concise and the spirit of Lambda expressions is met better than the second
+ * version.
+ * </p>
+ * @since 3.9
+ * @deprecated Use {@link org.apache.commons.lang3.function.Failable}.
+ */
+@Deprecated
+public class Functions {
+
+ /**
+ * A functional interface like {@link BiConsumer} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <O1> Consumed type 1.
+ * @param <O2> Consumed type 2.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiConsumer}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableBiConsumer<O1, O2, T extends Throwable> {
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object1 the first parameter for the consumable to accept
+ * @param object2 the second parameter for the consumable to accept
+ * @throws T Thrown when the consumer fails.
+ */
+ void accept(O1 object1, O2 object2) throws T;
+ }
+
+ /**
+ * A functional interface like {@link BiFunction} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <O1> Input type 1.
+ * @param <O2> Input type 2.
+ * @param <R> Return type.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiFunction}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableBiFunction<O1, O2, R, T extends Throwable> {
+
+ /**
+ * Applies this function.
+ *
+ * @param input1 the first input for the function
+ * @param input2 the second input for the function
+ * @return the result of the function
+ * @throws T Thrown when the function fails.
+ */
+ R apply(O1 input1, O2 input2) throws T;
+ }
+
+ /**
+ * A functional interface like {@link BiPredicate} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <O1> Predicate type 1.
+ * @param <O2> Predicate type 2.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableBiPredicate}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableBiPredicate<O1, O2, T extends Throwable> {
+
+ /**
+ * Tests the predicate.
+ *
+ * @param object1 the first object to test the predicate on
+ * @param object2 the second object to test the predicate on
+ * @return the predicate's evaluation
+ * @throws T if the predicate fails
+ */
+ boolean test(O1 object1, O2 object2) throws T;
+ }
+
+ /**
+ * A functional interface like {@link java.util.concurrent.Callable} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <R> Return type.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableCallable}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableCallable<R, T extends Throwable> {
+
+ /**
+ * Calls the callable.
+ *
+ * @return The value returned from the callable
+ * @throws T if the callable fails
+ */
+ R call() throws T;
+ }
+
+ /**
+ * A functional interface like {@link Consumer} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <O> Consumed type 1.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableConsumer}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableConsumer<O, T extends Throwable> {
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the parameter for the consumable to accept
+ * @throws T Thrown when the consumer fails.
+ */
+ void accept(O object) throws T;
+ }
+
+ /**
+ * A functional interface like {@link Function} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <I> Input type 1.
+ * @param <R> Return type.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableFunction}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableFunction<I, R, T extends Throwable> {
+
+ /**
+ * Applies this function.
+ *
+ * @param input the input for the function
+ * @return the result of the function
+ * @throws T Thrown when the function fails.
+ */
+ R apply(I input) throws T;
+ }
+
+ /**
+ * A functional interface like {@link Predicate} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <I> Predicate type 1.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailablePredicate}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailablePredicate<I, T extends Throwable> {
+
+ /**
+ * Tests the predicate.
+ *
+ * @param object the object to test the predicate on
+ * @return the predicate's evaluation
+ * @throws T if the predicate fails
+ */
+ boolean test(I object) throws T;
+ }
+
+ /**
+ * A functional interface like {@link Runnable} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableRunnable}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableRunnable<T extends Throwable> {
+
+ /**
+ * Runs the function.
+ *
+ * @throws T Thrown when the function fails.
+ */
+ void run() throws T;
+ }
+
+ /**
+ * A functional interface like {@link Supplier} that declares a {@link Throwable}.
+ *
+ * <p>TODO for 4.0: Move to org.apache.commons.lang3.function.</p>
+ *
+ * @param <R> Return type.
+ * @param <T> Thrown exception.
+ * @deprecated Use {@link org.apache.commons.lang3.function.FailableSupplier}.
+ */
+ @Deprecated
+ @FunctionalInterface
+ public interface FailableSupplier<R, T extends Throwable> {
+
+ /**
+ * Supplies an object
+ *
+ * @return a result
+ * @throws T if the supplier fails
+ */
+ R get() throws T;
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param object1 the first object to consume by {@code consumer}
+ * @param object2 the second object to consume by {@code consumer}
+ * @param <O1> the type of the first argument the consumer accepts
+ * @param <O2> the type of the second argument the consumer accepts
+ * @param <T> the type of checked exception the consumer may throw
+ */
+ public static <O1, O2, T extends Throwable> void accept(final FailableBiConsumer<O1, O2, T> consumer,
+ final O1 object1, final O2 object2) {
+ run(() -> consumer.accept(object1, object2));
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param object the object to consume by {@code consumer}
+ * @param <O> the type the consumer accepts
+ * @param <T> the type of checked exception the consumer may throw
+ */
+ public static <O, T extends Throwable> void accept(final FailableConsumer<O, T> consumer, final O object) {
+ run(() -> consumer.accept(object));
+ }
+
+ /**
+ * Applies a function and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param function the function to apply
+ * @param input1 the first input to apply {@code function} on
+ * @param input2 the second input to apply {@code function} on
+ * @param <O1> the type of the first argument the function accepts
+ * @param <O2> the type of the second argument the function accepts
+ * @param <O> the return type of the function
+ * @param <T> the type of checked exception the function may throw
+ * @return the value returned from the function
+ */
+ public static <O1, O2, O, T extends Throwable> O apply(final FailableBiFunction<O1, O2, O, T> function,
+ final O1 input1, final O2 input2) {
+ return get(() -> function.apply(input1, input2));
+ }
+
+ /**
+ * Applies a function and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param function the function to apply
+ * @param input the input to apply {@code function} on
+ * @param <I> the type of the argument the function accepts
+ * @param <O> the return type of the function
+ * @param <T> the type of checked exception the function may throw
+ * @return the value returned from the function
+ */
+ public static <I, O, T extends Throwable> O apply(final FailableFunction<I, O, T> function, final I input) {
+ return get(() -> function.apply(input));
+ }
+
+ /**
+ * Converts the given {@link FailableBiConsumer} into a standard {@link BiConsumer}.
+ *
+ * @param <O1> the type of the first argument of the consumers
+ * @param <O2> the type of the second argument of the consumers
+ * @param consumer a failable {@link BiConsumer}
+ * @return a standard {@link BiConsumer}
+ * @since 3.10
+ */
+ public static <O1, O2> BiConsumer<O1, O2> asBiConsumer(final FailableBiConsumer<O1, O2, ?> consumer) {
+ return (input1, input2) -> accept(consumer, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableBiFunction} into a standard {@link BiFunction}.
+ *
+ * @param <O1> the type of the first argument of the input of the functions
+ * @param <O2> the type of the second argument of the input of the functions
+ * @param <O> the type of the output of the functions
+ * @param function a {@link FailableBiFunction}
+ * @return a standard {@link BiFunction}
+ * @since 3.10
+ */
+ public static <O1, O2, O> BiFunction<O1, O2, O> asBiFunction(final FailableBiFunction<O1, O2, O, ?> function) {
+ return (input1, input2) -> apply(function, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableBiPredicate} into a standard {@link BiPredicate}.
+ *
+ * @param <O1> the type of the first argument used by the predicates
+ * @param <O2> the type of the second argument used by the predicates
+ * @param predicate a {@link FailableBiPredicate}
+ * @return a standard {@link BiPredicate}
+ * @since 3.10
+ */
+ public static <O1, O2> BiPredicate<O1, O2> asBiPredicate(final FailableBiPredicate<O1, O2, ?> predicate) {
+ return (input1, input2) -> test(predicate, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableCallable} into a standard {@link Callable}.
+ *
+ * @param <O> the type used by the callables
+ * @param callable a {@link FailableCallable}
+ * @return a standard {@link Callable}
+ * @since 3.10
+ */
+ public static <O> Callable<O> asCallable(final FailableCallable<O, ?> callable) {
+ return () -> call(callable);
+ }
+
+ /**
+ * Converts the given {@link FailableConsumer} into a standard {@link Consumer}.
+ *
+ * @param <I> the type used by the consumers
+ * @param consumer a {@link FailableConsumer}
+ * @return a standard {@link Consumer}
+ * @since 3.10
+ */
+ public static <I> Consumer<I> asConsumer(final FailableConsumer<I, ?> consumer) {
+ return input -> accept(consumer, input);
+ }
+
+ /**
+ * Converts the given {@link FailableFunction} into a standard {@link Function}.
+ *
+ * @param <I> the type of the input of the functions
+ * @param <O> the type of the output of the functions
+ * @param function a {code FailableFunction}
+ * @return a standard {@link Function}
+ * @since 3.10
+ */
+ public static <I, O> Function<I, O> asFunction(final FailableFunction<I, O, ?> function) {
+ return input -> apply(function, input);
+ }
+
+ /**
+ * Converts the given {@link FailablePredicate} into a standard {@link Predicate}.
+ *
+ * @param <I> the type used by the predicates
+ * @param predicate a {@link FailablePredicate}
+ * @return a standard {@link Predicate}
+ * @since 3.10
+ */
+ public static <I> Predicate<I> asPredicate(final FailablePredicate<I, ?> predicate) {
+ return input -> test(predicate, input);
+ }
+
+ /**
+ * Converts the given {@link FailableRunnable} into a standard {@link Runnable}.
+ *
+ * @param runnable a {@link FailableRunnable}
+ * @return a standard {@link Runnable}
+ * @since 3.10
+ */
+ public static Runnable asRunnable(final FailableRunnable<?> runnable) {
+ return () -> run(runnable);
+ }
+
+ /**
+ * Converts the given {@link FailableSupplier} into a standard {@link Supplier}.
+ *
+ * @param <O> the type supplied by the suppliers
+ * @param supplier a {@link FailableSupplier}
+ * @return a standard {@link Supplier}
+ * @since 3.10
+ */
+ public static <O> Supplier<O> asSupplier(final FailableSupplier<O, ?> supplier) {
+ return () -> get(supplier);
+ }
+
+ /**
+ * Calls a callable and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param callable the callable to call
+ * @param <O> the return type of the callable
+ * @param <T> the type of checked exception the callable may throw
+ * @return the value returned from the callable
+ */
+ public static <O, T extends Throwable> O call(final FailableCallable<O, T> callable) {
+ return get(callable::call);
+ }
+
+ /**
+ * Invokes a supplier, and returns the result.
+ *
+ * @param supplier The supplier to invoke.
+ * @param <O> The suppliers output type.
+ * @param <T> The type of checked exception, which the supplier can throw.
+ * @return The object, which has been created by the supplier
+ * @since 3.10
+ */
+ public static <O, T extends Throwable> O get(final FailableSupplier<O, T> supplier) {
+ try {
+ return supplier.get();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes a boolean supplier, and returns the result.
+ *
+ * @param supplier The boolean supplier to invoke.
+ * @param <T> The type of checked exception, which the supplier can throw.
+ * @return The boolean, which has been created by the supplier
+ */
+ private static <T extends Throwable> boolean getAsBoolean(final FailableBooleanSupplier<T> supplier) {
+ try {
+ return supplier.getAsBoolean();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Rethrows a {@link Throwable} as an unchecked exception. If the argument is already unchecked, namely a
+ * {@link RuntimeException} or {@link Error} then the argument will be rethrown without modification. If the
+ * exception is {@link IOException} then it will be wrapped into a {@link UncheckedIOException}. In every other
+ * cases the exception will be wrapped into a {@code
+ * UndeclaredThrowableException}
+ *
+ * <p>
+ * Note that there is a declared return type for this method, even though it never returns. The reason for that is
+ * to support the usual pattern:
+ * </p>
+ *
+ * <pre>
+ * throw rethrow(myUncheckedException);</pre>
+ *
+ * <p>
+ * instead of just calling the method. This pattern may help the Java compiler to recognize that at that point an
+ * exception will be thrown and the code flow analysis will not demand otherwise mandatory commands that could
+ * follow the method call, like a {@code return} statement from a value returning method.
+ * </p>
+ *
+ * @param throwable The throwable to rethrow possibly wrapped into an unchecked exception
+ * @return Never returns anything, this method never terminates normally.
+ */
+ public static RuntimeException rethrow(final Throwable throwable) {
+ Objects.requireNonNull(throwable, "throwable");
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+ if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ if (throwable instanceof IOException) {
+ throw new UncheckedIOException((IOException) throwable);
+ }
+ throw new UndeclaredThrowableException(throwable);
+ }
+
+ /**
+ * Runs a runnable and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param runnable The runnable to run
+ * @param <T> the type of checked exception the runnable may throw
+ */
+ public static <T extends Throwable> void run(final FailableRunnable<T> runnable) {
+ try {
+ runnable.run();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Converts the given collection into a {@link FailableStream}. The {@link FailableStream} consists of the
+ * collections elements. Shortcut for
+ *
+ * <pre>
+ * Functions.stream(collection.stream());</pre>
+ *
+ * @param collection The collection, which is being converted into a {@link FailableStream}.
+ * @param <O> The collections element type. (In turn, the result streams element type.)
+ * @return The created {@link FailableStream}.
+ * @since 3.10
+ */
+ public static <O> FailableStream<O> stream(final Collection<O> collection) {
+ return new FailableStream<>(collection.stream());
+ }
+
+ /**
+ * Converts the given stream into a {@link FailableStream}. The {@link FailableStream} consists of the same
+ * elements, than the input stream. However, failable lambdas, like {@link FailablePredicate},
+ * {@link FailableFunction}, and {@link FailableConsumer} may be applied, rather than {@link Predicate},
+ * {@link Function}, {@link Consumer}, etc.
+ *
+ * @param stream The stream, which is being converted into a {@link FailableStream}.
+ * @param <O> The streams element type.
+ * @return The created {@link FailableStream}.
+ * @since 3.10
+ */
+ public static <O> FailableStream<O> stream(final Stream<O> stream) {
+ return new FailableStream<>(stream);
+ }
+
+ /**
+ * Tests a predicate and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param predicate the predicate to test
+ * @param object1 the first input to test by {@code predicate}
+ * @param object2 the second input to test by {@code predicate}
+ * @param <O1> the type of the first argument the predicate tests
+ * @param <O2> the type of the second argument the predicate tests
+ * @param <T> the type of checked exception the predicate may throw
+ * @return the boolean value returned by the predicate
+ */
+ public static <O1, O2, T extends Throwable> boolean test(final FailableBiPredicate<O1, O2, T> predicate,
+ final O1 object1, final O2 object2) {
+ return getAsBoolean(() -> predicate.test(object1, object2));
+ }
+
+ /**
+ * Tests a predicate and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param predicate the predicate to test
+ * @param object the input to test by {@code predicate}
+ * @param <O> the type of argument the predicate tests
+ * @param <T> the type of checked exception the predicate may throw
+ * @return the boolean value returned by the predicate
+ */
+ public static <O, T extends Throwable> boolean test(final FailablePredicate<O, T> predicate, final O object) {
+ return getAsBoolean(() -> predicate.test(object));
+ }
+
+ /**
+ * A simple try-with-resources implementation, that can be used, if your objects do not implement the
+ * {@link AutoCloseable} interface. The method executes the {@code action}. The method guarantees, that <em>all</em>
+ * the {@code resources} are being executed, in the given order, afterwards, and regardless of success, or failure.
+ * If either the original action, or any of the resource action fails, then the <em>first</em> failure (AKA
+ * {@link Throwable}) is rethrown. Example use:
+ *
+ * <pre>
+ * {@code
+ * final FileInputStream fis = new FileInputStream("my.file");
+ * Functions.tryWithResources(useInputStream(fis), null, () -> fis.close());
+ * }</pre>
+ *
+ * @param action The action to execute. This object <em>will</em> always be invoked.
+ * @param errorHandler An optional error handler, which will be invoked finally, if any error occurred. The error
+ * handler will receive the first error, AKA {@link Throwable}.
+ * @param resources The resource actions to execute. <em>All</em> resource actions will be invoked, in the given
+ * order. A resource action is an instance of {@link FailableRunnable}, which will be executed.
+ * @see #tryWithResources(FailableRunnable, FailableRunnable...)
+ */
+ @SafeVarargs
+ public static void tryWithResources(final FailableRunnable<? extends Throwable> action,
+ final FailableConsumer<Throwable, ? extends Throwable> errorHandler,
+ final FailableRunnable<? extends Throwable>... resources) {
+ final org.apache.commons.lang3.function.FailableRunnable<?>[] fr = new org.apache.commons.lang3.function.FailableRunnable[resources.length];
+ Arrays.setAll(fr, i -> () -> resources[i].run());
+ Failable.tryWithResources(action::run, errorHandler != null ? errorHandler::accept : null, fr);
+ }
+
+ /**
+ * A simple try-with-resources implementation, that can be used, if your objects do not implement the
+ * {@link AutoCloseable} interface. The method executes the {@code action}. The method guarantees, that <em>all</em>
+ * the {@code resources} are being executed, in the given order, afterwards, and regardless of success, or failure.
+ * If either the original action, or any of the resource action fails, then the <em>first</em> failure (AKA
+ * {@link Throwable}) is rethrown. Example use:
+ *
+ * <pre>
+ * {@code
+ * final FileInputStream fis = new FileInputStream("my.file");
+ * Functions.tryWithResources(useInputStream(fis), () -> fis.close());
+ * }</pre>
+ *
+ * @param action The action to execute. This object <em>will</em> always be invoked.
+ * @param resources The resource actions to execute. <em>All</em> resource actions will be invoked, in the given
+ * order. A resource action is an instance of {@link FailableRunnable}, which will be executed.
+ * @see #tryWithResources(FailableRunnable, FailableConsumer, FailableRunnable...)
+ */
+ @SafeVarargs
+ public static void tryWithResources(final FailableRunnable<? extends Throwable> action,
+ final FailableRunnable<? extends Throwable>... resources) {
+ tryWithResources(action, null, resources);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/IntegerRange.java b/src/main/java/org/apache/commons/lang3/IntegerRange.java
new file mode 100644
index 000000000..f3666ae89
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/IntegerRange.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+/**
+ * Specializes {@link NumberRange} for {@link Integer}s.
+ *
+ * <p>
+ * This class is not designed to interoperate with other NumberRanges
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public final class IntegerRange extends NumberRange<Integer> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ */
+ public static IntegerRange of(final int fromInclusive, final int toInclusive) {
+ return of(Integer.valueOf(fromInclusive), Integer.valueOf(toInclusive));
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ * @throws IllegalArgumentException if either element is null.
+ */
+ public static IntegerRange of(final Integer fromInclusive, final Integer toInclusive) {
+ return new IntegerRange(fromInclusive, toInclusive);
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param number1 the first element, not null
+ * @param number2 the second element, not null
+ * @throws NullPointerException when element1 is null.
+ * @throws NullPointerException when element2 is null.
+ */
+ private IntegerRange(final Integer number1, final Integer number2) {
+ super(number1, number2, null);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/JavaVersion.java b/src/main/java/org/apache/commons/lang3/JavaVersion.java
new file mode 100644
index 000000000..4fc8073be
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/JavaVersion.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * An enum representing all the versions of the Java specification.
+ * This is intended to mirror available values from the
+ * <em>java.specification.version</em> System property.
+ *
+ * @since 3.0
+ */
+public enum JavaVersion {
+
+ /**
+ * The Java version reported by Android. This is not an official Java version number.
+ */
+ JAVA_0_9(1.5f, "0.9"),
+
+ /**
+ * Java 1.1.
+ */
+ JAVA_1_1(1.1f, "1.1"),
+
+ /**
+ * Java 1.2.
+ */
+ JAVA_1_2(1.2f, "1.2"),
+
+ /**
+ * Java 1.3.
+ */
+ JAVA_1_3(1.3f, "1.3"),
+
+ /**
+ * Java 1.4.
+ */
+ JAVA_1_4(1.4f, "1.4"),
+
+ /**
+ * Java 1.5.
+ */
+ JAVA_1_5(1.5f, "1.5"),
+
+ /**
+ * Java 1.6.
+ */
+ JAVA_1_6(1.6f, "1.6"),
+
+ /**
+ * Java 1.7.
+ */
+ JAVA_1_7(1.7f, "1.7"),
+
+ /**
+ * Java 1.8.
+ */
+ JAVA_1_8(1.8f, "1.8"),
+
+ /**
+ * Java 1.9.
+ *
+ * @deprecated As of release 3.5, replaced by {@link #JAVA_9}
+ */
+ @Deprecated
+ JAVA_1_9(9.0f, "9"),
+
+ /**
+ * Java 9.
+ *
+ * @since 3.5
+ */
+ JAVA_9(9.0f, "9"),
+
+ /**
+ * Java 10.
+ *
+ * @since 3.7
+ */
+ JAVA_10(10.0f, "10"),
+
+ /**
+ * Java 11.
+ *
+ * @since 3.8
+ */
+ JAVA_11(11.0f, "11"),
+
+ /**
+ * Java 12.
+ *
+ * @since 3.9
+ */
+ JAVA_12(12.0f, "12"),
+
+ /**
+ * Java 13.
+ *
+ * @since 3.9
+ */
+ JAVA_13(13.0f, "13"),
+
+ /**
+ * Java 14.
+ *
+ * @since 3.11
+ */
+ JAVA_14(14.0f, "14"),
+
+ /**
+ * Java 15.
+ *
+ * @since 3.11
+ */
+ JAVA_15(15.0f, "15"),
+
+ /**
+ * Java 16.
+ *
+ * @since 3.11
+ */
+ JAVA_16(16.0f, "16"),
+
+ /**
+ * Java 17.
+ *
+ * @since 3.12.0
+ */
+ JAVA_17(17.0f, "17"),
+
+ /**
+ * Java 18.
+ *
+ * @since 3.13.0
+ */
+ JAVA_18(18.0f, "18"),
+
+ /**
+ * The most recent java version. Mainly introduced to avoid to break when a new version of Java is used.
+ */
+ 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.
+ *
+ * <p>For example:<br>
+ * {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}</p>
+ *
+ * @param requiredVersion the version to check against, not null
+ * @return true if this version is equal to or greater than the specified version
+ */
+ public boolean atLeast(final JavaVersion requiredVersion) {
+ return this.value >= requiredVersion.value;
+ }
+
+ /**
+ * Whether this version of Java is at most the version of Java passed in.
+ *
+ * <p>For example:<br>
+ * {@code myVersion.atMost(JavaVersion.JAVA_1_4)}</p>
+ *
+ * @param requiredVersion the version to check against, not null
+ * @return true if this version is equal to or greater than the specified version
+ * @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 <b>null</b> 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
+ * internally.
+ *
+ * @param versionStr the Java version as string
+ * @return the corresponding enumeration constant or <b>null</b> if the
+ * version is unknown
+ */
+ static JavaVersion get(final String versionStr) {
+ if (versionStr == null) {
+ return null;
+ }
+ switch (versionStr) {
+ case "0.9":
+ return JAVA_0_9;
+ case "1.1":
+ return JAVA_1_1;
+ case "1.2":
+ return JAVA_1_2;
+ case "1.3":
+ return JAVA_1_3;
+ case "1.4":
+ return JAVA_1_4;
+ case "1.5":
+ return JAVA_1_5;
+ case "1.6":
+ return JAVA_1_6;
+ case "1.7":
+ return JAVA_1_7;
+ case "1.8":
+ return JAVA_1_8;
+ case "9":
+ return JAVA_9;
+ case "10":
+ return JAVA_10;
+ case "11":
+ return JAVA_11;
+ case "12":
+ return JAVA_12;
+ case "13":
+ return JAVA_13;
+ case "14":
+ return JAVA_14;
+ case "15":
+ return JAVA_15;
+ case "16":
+ return JAVA_16;
+ case "17":
+ return JAVA_17;
+ case "18":
+ return JAVA_18;
+ default:
+ final float v = toFloatVersion(versionStr);
+ if ((v - 1.) < 1.) { // then we need to check decimals > .9
+ final int firstComma = Math.max(versionStr.indexOf('.'), versionStr.indexOf(','));
+ final int end = Math.max(versionStr.length(), versionStr.indexOf(',', firstComma));
+ if (Float.parseFloat(versionStr.substring(firstComma + 1, end)) > .9f) {
+ return JAVA_RECENT;
+ }
+ } else if (v > 10) {
+ return JAVA_RECENT;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * The string value is overridden to return the standard name.
+ *
+ * <p>For example, {@code "1.5"}.</p>
+ *
+ * @return the name, not null
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Gets the Java Version from the system or 99.0 if the {@code java.specification.version} system property is not set.
+ *
+ * @return the value of {@code java.specification.version} system property or 99.0 if it is not set.
+ */
+ private static float maxVersion() {
+ final float v = toFloatVersion(System.getProperty("java.specification.version", "99.0"));
+ return v > 0 ? v : 99f;
+ }
+
+ /**
+ * Parses a float value from a String.
+ *
+ * @param value the String to parse.
+ * @return the float value represented by the string or -1 if the given String can not be parsed.
+ */
+ private static float toFloatVersion(final String value) {
+ final int defaultReturnValue = -1;
+ if (!value.contains(".")) {
+ return NumberUtils.toFloat(value, defaultReturnValue);
+ }
+ final String[] toParse = value.split("\\.");
+ if (toParse.length >= 2) {
+ return NumberUtils.toFloat(toParse[0] + '.' + toParse[1], defaultReturnValue);
+ }
+ return defaultReturnValue;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java
new file mode 100644
index 000000000..c9c22fb9c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java
@@ -0,0 +1,346 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Operations to assist when working with a {@link Locale}.
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * @since 2.2
+ */
+public class LocaleUtils {
+ private static final char UNDERSCORE = '_';
+ private static final char DASH = '-';
+
+ // class to avoid synchronization (Init on demand)
+ static class SyncAvoid {
+ /** Unmodifiable list of available locales. */
+ private static final List<Locale> AVAILABLE_LOCALE_LIST;
+ /** Unmodifiable set of available locales. */
+ private static final Set<Locale> AVAILABLE_LOCALE_SET;
+
+ static {
+ final List<Locale> list = new ArrayList<>(Arrays.asList(Locale.getAvailableLocales())); // extra safe
+ AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list);
+ AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list));
+ }
+ }
+
+ /** Concurrent map of language locales by country. */
+ private static final ConcurrentMap<String, List<Locale>> cLanguagesByCountry =
+ new ConcurrentHashMap<>();
+
+ /** Concurrent map of country locales by language. */
+ private static final ConcurrentMap<String, List<Locale>> cCountriesByLanguage =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Obtains an unmodifiable list of installed locales.
+ *
+ * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
+ * It is more efficient, as the JDK method must create a new array each
+ * time it is called.</p>
+ *
+ * @return the unmodifiable list of available locales
+ */
+ public static List<Locale> availableLocaleList() {
+ return SyncAvoid.AVAILABLE_LOCALE_LIST;
+ }
+
+ private static List<Locale> availableLocaleList(final Predicate<Locale> predicate) {
+ return availableLocaleList().stream().filter(predicate).collect(Collectors.toList());
+ }
+
+ /**
+ * Obtains an unmodifiable set of installed locales.
+ *
+ * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
+ * It is more efficient, as the JDK method must create a new array each
+ * time it is called.</p>
+ *
+ * @return the unmodifiable set of available locales
+ */
+ public static Set<Locale> availableLocaleSet() {
+ return SyncAvoid.AVAILABLE_LOCALE_SET;
+ }
+
+ /**
+ * Obtains the list of countries supported for a given language.
+ *
+ * <p>This method takes a language code and searches to find the
+ * countries available for that language. Variant locales are removed.</p>
+ *
+ * @param languageCode the 2 letter language code, null returns empty
+ * @return an unmodifiable List of Locale objects, not null
+ */
+ public static List<Locale> countriesByLanguage(final String languageCode) {
+ if (languageCode == null) {
+ return Collections.emptyList();
+ }
+ return cCountriesByLanguage.computeIfAbsent(languageCode, lc -> Collections.unmodifiableList(
+ availableLocaleList(locale -> languageCode.equals(locale.getLanguage()) && !locale.getCountry().isEmpty() && locale.getVariant().isEmpty())));
+ }
+
+ /**
+ * Checks if the locale specified is in the set of available locales.
+ *
+ * @param locale the Locale object to check if it is available
+ * @return true if the locale is a known locale
+ */
+ public static boolean isAvailableLocale(final Locale locale) {
+ return availableLocaleSet().contains(locale);
+ }
+
+ /**
+ * Checks whether the given String is a ISO 3166 alpha-2 country code.
+ *
+ * @param str the String to check
+ * @return true, is the given String is a ISO 3166 compliant country code.
+ */
+ private static boolean isISO3166CountryCode(final String str) {
+ return StringUtils.isAllUpperCase(str) && str.length() == 2;
+ }
+
+ /**
+ * Checks whether the given String is a ISO 639 compliant language code.
+ *
+ * @param str the String to check.
+ * @return true, if the given String is a ISO 639 compliant language code.
+ */
+ private static boolean isISO639LanguageCode(final String str) {
+ return StringUtils.isAllLowerCase(str) && (str.length() == 2 || str.length() == 3);
+ }
+
+ /**
+ * Checks whether the given String is a UN M.49 numeric area code.
+ *
+ * @param str the String to check
+ * @return true, is the given String is a UN M.49 numeric area code.
+ */
+ private static boolean isNumericAreaCode(final String str) {
+ return StringUtils.isNumeric(str) && str.length() == 3;
+ }
+
+ /**
+ * Obtains the list of languages supported for a given country.
+ *
+ * <p>This method takes a country code and searches to find the
+ * languages available for that country. Variant locales are removed.</p>
+ *
+ * @param countryCode the 2-letter country code, null returns empty
+ * @return an unmodifiable List of Locale objects, not null
+ */
+ public static List<Locale> languagesByCountry(final String countryCode) {
+ if (countryCode == null) {
+ return Collections.emptyList();
+ }
+ return cLanguagesByCountry.computeIfAbsent(countryCode,
+ k -> Collections.unmodifiableList(availableLocaleList(locale -> countryCode.equals(locale.getCountry()) && locale.getVariant().isEmpty())));
+ }
+
+ /**
+ * Obtains the list of locales to search through when performing
+ * a locale search.
+ *
+ * <pre>
+ * localeLookupList(Locale("fr", "CA", "xxx"))
+ * = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr")]
+ * </pre>
+ *
+ * @param locale the locale to start from
+ * @return the unmodifiable list of Locale objects, 0 being locale, not null
+ */
+ public static List<Locale> localeLookupList(final Locale locale) {
+ return localeLookupList(locale, locale);
+ }
+
+ /**
+ * Obtains the list of locales to search through when performing
+ * a locale search.
+ *
+ * <pre>
+ * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
+ * = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr"), Locale("en"]
+ * </pre>
+ *
+ * <p>The result list begins with the most specific locale, then the
+ * next more general and so on, finishing with the default locale.
+ * The list will never contain the same locale twice.</p>
+ *
+ * @param locale the locale to start from, null returns empty list
+ * @param defaultLocale the default locale to use if no other is found
+ * @return the unmodifiable list of Locale objects, 0 being locale, not null
+ */
+ public static List<Locale> localeLookupList(final Locale locale, final Locale defaultLocale) {
+ final List<Locale> list = new ArrayList<>(4);
+ if (locale != null) {
+ list.add(locale);
+ if (!locale.getVariant().isEmpty()) {
+ list.add(new Locale(locale.getLanguage(), locale.getCountry()));
+ }
+ if (!locale.getCountry().isEmpty()) {
+ list.add(new Locale(locale.getLanguage(), StringUtils.EMPTY));
+ }
+ if (!list.contains(defaultLocale)) {
+ list.add(defaultLocale);
+ }
+ }
+ return Collections.unmodifiableList(list);
+ }
+
+ /**
+ * Tries to parse a locale from the given String.
+ *
+ * @param str the String to parse a locale from.
+ * @return a Locale instance parsed from the given String.
+ * @throws IllegalArgumentException if the given String can not be parsed.
+ */
+ private static Locale parseLocale(final String str) {
+ if (isISO639LanguageCode(str)) {
+ return new Locale(str);
+ }
+
+ final String[] segments = str.indexOf(UNDERSCORE) != -1
+ ? str.split(String.valueOf(UNDERSCORE), -1)
+ : str.split(String.valueOf(DASH), -1);
+ final String language = segments[0];
+ if (segments.length == 2) {
+ final String country = segments[1];
+ if (isISO639LanguageCode(language) && isISO3166CountryCode(country) ||
+ isNumericAreaCode(country)) {
+ return new Locale(language, country);
+ }
+ } else if (segments.length == 3) {
+ final String country = segments[1];
+ final String variant = segments[2];
+ if (isISO639LanguageCode(language) &&
+ (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) &&
+ !variant.isEmpty()) {
+ return new Locale(language, country, variant);
+ }
+ }
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+
+ /**
+ * Returns the given locale if non-{@code null}, otherwise {@link Locale#getDefault()}.
+ *
+ * @param locale a locale or {@code null}.
+ * @return the given locale if non-{@code null}, otherwise {@link Locale#getDefault()}.
+ * @since 3.12.0
+ */
+ public static Locale toLocale(final Locale locale) {
+ return locale != null ? locale : Locale.getDefault();
+ }
+
+ /**
+ * Converts a String to a Locale.
+ *
+ * <p>This method takes the string format of a locale and creates the
+ * locale object from it.</p>
+ *
+ * <pre>
+ * LocaleUtils.toLocale("") = new Locale("", "")
+ * LocaleUtils.toLocale("en") = new Locale("en", "")
+ * LocaleUtils.toLocale("en_GB") = new Locale("en", "GB")
+ * LocaleUtils.toLocale("en-GB") = new Locale("en", "GB")
+ * LocaleUtils.toLocale("en_001") = new Locale("en", "001")
+ * LocaleUtils.toLocale("en_GB_xxx") = new Locale("en", "GB", "xxx") (#)
+ * </pre>
+ *
+ * <p>(#) The behavior of the JDK variant constructor changed between JDK1.3 and JDK1.4.
+ * In JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't.
+ * Thus, the result from getVariant() may vary depending on your JDK.</p>
+ *
+ * <p>This method validates the input strictly.
+ * The language code must be lowercase.
+ * The country code must be uppercase.
+ * The separator must be an underscore or a dash.
+ * The length must be correct.
+ * </p>
+ *
+ * @param str the locale String to convert, null returns null
+ * @return a Locale, null if null input
+ * @throws IllegalArgumentException if the string is an invalid format
+ * @see Locale#forLanguageTag(String)
+ */
+ public static Locale toLocale(final String str) {
+ if (str == null) {
+ // TODO Should this return the default locale?
+ return null;
+ }
+ if (str.isEmpty()) { // LANG-941 - JDK 8 introduced an empty locale where all fields are blank
+ return new Locale(StringUtils.EMPTY, StringUtils.EMPTY);
+ }
+ if (str.contains("#")) { // LANG-879 - Cannot handle Java 7 script & extensions
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ final int len = str.length();
+ if (len < 2) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ final char ch0 = str.charAt(0);
+ if (ch0 == UNDERSCORE || ch0 == DASH) {
+ if (len < 3) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ if (len == 3) {
+ return new Locale(StringUtils.EMPTY, str.substring(1, 3));
+ }
+ if (len < 5) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ if (str.charAt(3) != ch0) {
+ throw new IllegalArgumentException("Invalid locale format: " + str);
+ }
+ return new Locale(StringUtils.EMPTY, str.substring(1, 3), str.substring(4));
+ }
+
+ return parseLocale(str);
+ }
+
+ /**
+ * {@link LocaleUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code LocaleUtils.toLocale("en_GB");}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public LocaleUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/LongRange.java b/src/main/java/org/apache/commons/lang3/LongRange.java
new file mode 100644
index 000000000..ec1639557
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/LongRange.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+/**
+ * Specializes {@link NumberRange} for {@link Long}s.
+ *
+ * <p>
+ * This class is not designed to interoperate with other NumberRanges
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public final class LongRange extends NumberRange<Long> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ */
+ public static LongRange of(final long fromInclusive, final long toInclusive) {
+ return of(Long.valueOf(fromInclusive), Long.valueOf(toInclusive));
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>
+ * The range uses the natural ordering of the elements to determine where values lie in the range.
+ * </p>
+ *
+ * <p>
+ * The arguments may be passed in the order (min,max) or (max,min). The getMinimum and getMaximum methods will return the correct values.
+ * </p>
+ *
+ * @param fromInclusive the first value that defines the edge of the range, inclusive.
+ * @param toInclusive the second value that defines the edge of the range, inclusive.
+ * @return the range object, not null.
+ * @throws IllegalArgumentException if either element is null.
+ */
+ public static LongRange of(final Long fromInclusive, final Long toInclusive) {
+ return new LongRange(fromInclusive, toInclusive);
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param number1 the first element, not null
+ * @param number2 the second element, not null
+ * @throws NullPointerException when element1 is null.
+ * @throws NullPointerException when element2 is null.
+ */
+ private LongRange(final Long number1, final Long number2) {
+ super(number1, number2, null);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/NotImplementedException.java b/src/main/java/org/apache/commons/lang3/NotImplementedException.java
new file mode 100644
index 000000000..783b8a259
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/NotImplementedException.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+/**
+ * Thrown to indicate that a block of code has not been implemented.
+ * This exception supplements {@link UnsupportedOperationException}
+ * by providing a more semantically rich description of the problem.
+ *
+ * <p>{@link NotImplementedException} represents the case where the
+ * author has yet to implement the logic at this point in the program.
+ * This can act as an exception based TODO tag.</p>
+ *
+ * <pre>
+ * public void foo() {
+ * try {
+ * // do something that throws an Exception
+ * } catch (Exception ex) {
+ * // don't know what to do here yet
+ * throw new NotImplementedException("TODO", ex);
+ * }
+ * }
+ * </pre>
+ *
+ * This class was originally added in Lang 2.0, but removed in 3.0.
+ *
+ * @since 3.2
+ */
+public class NotImplementedException extends UnsupportedOperationException {
+
+ private static final long serialVersionUID = 20131021L;
+
+ /** A resource for more information regarding the lack of implementation. */
+ private final String code;
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @since 3.10
+ */
+ public NotImplementedException() {
+ this.code = null;
+ }
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @param message description of the exception
+ * @since 3.2
+ */
+ public NotImplementedException(final String message) {
+ this(message, (String) null);
+ }
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @param cause cause of the exception
+ * @since 3.2
+ */
+ public NotImplementedException(final Throwable cause) {
+ this(cause, null);
+ }
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @param message description of the exception
+ * @param cause cause of the exception
+ * @since 3.2
+ */
+ public NotImplementedException(final String message, final Throwable cause) {
+ this(message, cause, null);
+ }
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @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 String message, final String code) {
+ super(message);
+ this.code = code;
+ }
+
+ /**
+ * Constructs a NotImplementedException.
+ *
+ * @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 Throwable cause, final String code) {
+ super(cause);
+ 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
+ * tracker ID or a URL.
+ *
+ * @return a code indicating a resource for more information regarding the lack of implementation
+ */
+ public String getCode() {
+ return this.code;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/NumberRange.java b/src/main/java/org/apache/commons/lang3/NumberRange.java
new file mode 100644
index 000000000..96afc3a5d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/NumberRange.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import java.util.Comparator;
+
+/**
+ * Specializes {@link Range} for {@link Number}s.
+ * <p>
+ * We only offer specializations for Integer, Long, and Double (like Java Streams).
+ * </p>
+ *
+ * @param <N> The Number class.
+ * @since 3.13.0
+ */
+public class NumberRange<N extends Number> extends Range<N> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates an instance.
+ *
+ * @param number1 the first element, not null
+ * @param number2 the second element, not null
+ * @param comp the comparator to be used, null for natural ordering
+ * @throws NullPointerException when element1 is null.
+ * @throws NullPointerException when element2 is null.
+ */
+ public NumberRange(final N number1, final N number2, final Comparator<N> comp) {
+ super(number1, number2, comp);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ObjectUtils.java b/src/main/java/org/apache/commons/lang3/ObjectUtils.java
new file mode 100644
index 000000000..c1208fdd5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ObjectUtils.java
@@ -0,0 +1,1388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.exception.CloneFailedException;
+import org.apache.commons.lang3.function.Suppliers;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.text.StrBuilder;
+import org.apache.commons.lang3.time.DurationUtils;
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * Operations on {@link Object}.
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will generally not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ */
+//@Immutable
+@SuppressWarnings("deprecation") // deprecated class StrBuilder is imported
+// because it is part of the signature of deprecated methods
+public class ObjectUtils {
+
+ /**
+ * Class used as a null placeholder where {@code null}
+ * has another meaning.
+ *
+ * <p>For example, in a {@link HashMap} the
+ * {@link java.util.HashMap#get(Object)} method returns
+ * {@code null} if the {@link Map} contains {@code null} or if there is
+ * no matching key. The {@code null} placeholder can be used to distinguish
+ * between these two cases.</p>
+ *
+ * <p>Another example is {@link Hashtable}, where {@code null}
+ * cannot be stored.</p>
+ */
+ public static class Null implements Serializable {
+ /**
+ * Required for serialization support. Declare serialization compatibility with Commons Lang 1.0
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 7092611880189329093L;
+
+ /**
+ * Restricted constructor - singleton.
+ */
+ Null() {
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ *
+ * @return the singleton value
+ */
+ private Object readResolve() {
+ return NULL;
+ }
+ }
+
+ private static final char AT_SIGN = '@';
+
+ /**
+ * Singleton used as a {@code null} placeholder where
+ * {@code null} has another meaning.
+ *
+ * <p>For example, in a {@link HashMap} the
+ * {@link java.util.HashMap#get(Object)} method returns
+ * {@code null} if the {@link Map} contains {@code null} or if there
+ * is no matching key. The {@code null} placeholder can be used to
+ * distinguish between these two cases.</p>
+ *
+ * <p>Another example is {@link Hashtable}, where {@code null}
+ * cannot be stored.</p>
+ *
+ * <p>This instance is Serializable.</p>
+ */
+ public static final Null NULL = new Null();
+
+ /**
+ * Checks if all values in the array are not {@code nulls}.
+ *
+ * <p>
+ * If any value is {@code null} or the array is {@code null} then
+ * {@code false} is returned. If all elements in array are not
+ * {@code null} or the array is empty (contains no elements) {@code true}
+ * is returned.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.allNotNull(*) = true
+ * ObjectUtils.allNotNull(*, *) = true
+ * ObjectUtils.allNotNull(null) = false
+ * ObjectUtils.allNotNull(null, null) = false
+ * ObjectUtils.allNotNull(null, *) = false
+ * ObjectUtils.allNotNull(*, null) = false
+ * ObjectUtils.allNotNull(*, *, null, *) = false
+ * </pre>
+ *
+ * @param values the values to test, may be {@code null} or empty
+ * @return {@code false} if there is at least one {@code null} value in the array or the array is {@code null},
+ * {@code true} if all values in the array are not {@code null}s or array contains no elements.
+ * @since 3.5
+ */
+ public static boolean allNotNull(final Object... values) {
+ return values != null && Stream.of(values).noneMatch(Objects::isNull);
+ }
+
+ /**
+ * Checks if all values in the given array are {@code null}.
+ *
+ * <p>
+ * If all the values are {@code null} or the array is {@code null}
+ * or empty, then {@code true} is returned, otherwise {@code false} is returned.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.allNull(*) = false
+ * ObjectUtils.allNull(*, null) = false
+ * ObjectUtils.allNull(null, *) = false
+ * ObjectUtils.allNull(null, null, *, *) = false
+ * ObjectUtils.allNull(null) = true
+ * ObjectUtils.allNull(null, null) = true
+ * </pre>
+ *
+ * @param values the values to test, may be {@code null} or empty
+ * @return {@code true} if all values in the array are {@code null}s,
+ * {@code false} if there is at least one non-null value in the array.
+ * @since 3.11
+ */
+ public static boolean allNull(final Object... values) {
+ return !anyNotNull(values);
+ }
+
+ /**
+ * Checks if any value in the given array is not {@code null}.
+ *
+ * <p>
+ * If all the values are {@code null} or the array is {@code null}
+ * or empty then {@code false} is returned. Otherwise {@code true} is returned.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.anyNotNull(*) = true
+ * ObjectUtils.anyNotNull(*, null) = true
+ * ObjectUtils.anyNotNull(null, *) = true
+ * ObjectUtils.anyNotNull(null, null, *, *) = true
+ * ObjectUtils.anyNotNull(null) = false
+ * ObjectUtils.anyNotNull(null, null) = false
+ * </pre>
+ *
+ * @param values the values to test, may be {@code null} or empty
+ * @return {@code true} if there is at least one non-null value in the array,
+ * {@code false} if all values in the array are {@code null}s.
+ * If the array is {@code null} or empty {@code false} is also returned.
+ * @since 3.5
+ */
+ public static boolean anyNotNull(final Object... values) {
+ return firstNonNull(values) != null;
+ }
+
+ /**
+ * Checks if any value in the given array is {@code null}.
+ *
+ * <p>
+ * If any of the values are {@code null} or the array is {@code null},
+ * then {@code true} is returned, otherwise {@code false} is returned.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.anyNull(*) = false
+ * ObjectUtils.anyNull(*, *) = false
+ * ObjectUtils.anyNull(null) = true
+ * ObjectUtils.anyNull(null, null) = true
+ * ObjectUtils.anyNull(null, *) = true
+ * ObjectUtils.anyNull(*, null) = true
+ * ObjectUtils.anyNull(*, *, null, *) = true
+ * </pre>
+ *
+ * @param values the values to test, may be {@code null} or empty
+ * @return {@code true} if there is at least one {@code null} value in the array,
+ * {@code false} if all the values are non-null.
+ * If the array is {@code null} or empty, {@code true} is also returned.
+ * @since 3.11
+ */
+ public static boolean anyNull(final Object... values) {
+ return !allNotNull(values);
+ }
+
+ /**
+ * Clone an object.
+ *
+ * @param <T> the type of the object
+ * @param obj the object to clone, null returns null
+ * @return the clone if the object implements {@link Cloneable} otherwise {@code null}
+ * @throws CloneFailedException if the object is cloneable and the clone operation fails
+ * @since 3.0
+ */
+ public static <T> T clone(final T obj) {
+ if (obj instanceof Cloneable) {
+ final Object result;
+ if (isArray(obj)) {
+ final Class<?> componentType = obj.getClass().getComponentType();
+ if (componentType.isPrimitive()) {
+ int length = Array.getLength(obj);
+ result = Array.newInstance(componentType, length);
+ while (length-- > 0) {
+ Array.set(result, length, Array.get(obj, length));
+ }
+ } else {
+ result = ((Object[]) obj).clone();
+ }
+ } else {
+ try {
+ final Method clone = obj.getClass().getMethod("clone");
+ result = clone.invoke(obj);
+ } catch (final NoSuchMethodException e) {
+ throw new CloneFailedException("Cloneable type "
+ + obj.getClass().getName()
+ + " has no clone method", e);
+ } catch (final IllegalAccessException e) {
+ throw new CloneFailedException("Cannot clone Cloneable type "
+ + obj.getClass().getName(), e);
+ } catch (final InvocationTargetException e) {
+ throw new CloneFailedException("Exception cloning Cloneable type "
+ + obj.getClass().getName(), e.getCause());
+ }
+ }
+ @SuppressWarnings("unchecked") // OK because input is of type T
+ final T checked = (T) result;
+ return checked;
+ }
+
+ return null;
+ }
+
+ /**
+ * Clone an object if possible.
+ *
+ * <p>This method is similar to {@link #clone(Object)}, but will return the provided
+ * instance as the return value instead of {@code null} if the instance
+ * is not cloneable. This is more convenient if the caller uses different
+ * implementations (e.g. of a service) and some of the implementations do not allow concurrent
+ * processing or have state. In such cases the implementation can simply provide a proper
+ * clone implementation and the caller's code does not have to change.</p>
+ *
+ * @param <T> the type of the object
+ * @param obj the object to clone, null returns null
+ * @return the clone if the object implements {@link Cloneable} otherwise the object itself
+ * @throws CloneFailedException if the object is cloneable and the clone operation fails
+ * @since 3.0
+ */
+ public static <T> T cloneIfPossible(final T obj) {
+ final T clone = clone(obj);
+ return clone == null ? obj : clone;
+ }
+
+ /**
+ * Null safe comparison of Comparables.
+ * {@code null} is assumed to be less than a non-{@code null} value.
+ * <p>TODO Move to ComparableUtils.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param c1 the first comparable, may be null
+ * @param c2 the second comparable, may be null
+ * @return a negative value if c1 &lt; c2, zero if c1 = c2
+ * and a positive value if c1 &gt; c2
+ */
+ public static <T extends Comparable<? super T>> int compare(final T c1, final T c2) {
+ return compare(c1, c2, false);
+ }
+
+ /**
+ * Null safe comparison of Comparables.
+ * <p>TODO Move to ComparableUtils.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param c1 the first comparable, may be null
+ * @param c2 the second comparable, may be null
+ * @param nullGreater if true {@code null} is considered greater
+ * than a non-{@code null} value or if false {@code null} is
+ * considered less than a Non-{@code null} value
+ * @return a negative value if c1 &lt; c2, zero if c1 = c2
+ * and a positive value if c1 &gt; c2
+ * @see java.util.Comparator#compare(Object, Object)
+ */
+ public static <T extends Comparable<? super T>> int compare(final T c1, final T c2, final boolean nullGreater) {
+ if (c1 == c2) {
+ return 0;
+ }
+ if (c1 == null) {
+ return nullGreater ? 1 : -1;
+ }
+ if (c2 == null) {
+ return nullGreater ? -1 : 1;
+ }
+ return c1.compareTo(c2);
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static boolean MAGIC_FLAG = ObjectUtils.CONST(true);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the boolean value to return
+ * @return the boolean v, unchanged
+ * @since 3.2
+ */
+ public static boolean CONST(final boolean v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static byte MAGIC_BYTE = ObjectUtils.CONST((byte) 127);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the byte value to return
+ * @return the byte v, unchanged
+ * @since 3.2
+ */
+ public static byte CONST(final byte v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static char MAGIC_CHAR = ObjectUtils.CONST('a');
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the char value to return
+ * @return the char v, unchanged
+ * @since 3.2
+ */
+ public static char CONST(final char v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the double value to return
+ * @return the double v, unchanged
+ * @since 3.2
+ */
+ public static double CONST(final double v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the float value to return
+ * @return the float v, unchanged
+ * @since 3.2
+ */
+ public static float CONST(final float v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static int MAGIC_INT = ObjectUtils.CONST(123);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the int value to return
+ * @return the int v, unchanged
+ * @since 3.2
+ */
+ public static int CONST(final int v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static long MAGIC_LONG = ObjectUtils.CONST(123L);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the long value to return
+ * @return the long v, unchanged
+ * @since 3.2
+ */
+ public static long CONST(final long v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static short MAGIC_SHORT = ObjectUtils.CONST((short) 123);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the short value to return
+ * @return the short v, unchanged
+ * @since 3.2
+ */
+ public static short CONST(final short v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static String MAGIC_STRING = ObjectUtils.CONST("abc");
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param <T> the Object type
+ * @param v the genericized Object value to return (typically a String).
+ * @return the genericized Object v, unchanged (typically a String).
+ * @since 3.2
+ */
+ public static <T> T CONST(final T v) {
+ return v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static byte MAGIC_BYTE = ObjectUtils.CONST_BYTE(127);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the byte literal (as an int) value to return
+ * @throws IllegalArgumentException if the value passed to v
+ * is larger than a byte, that is, smaller than -128 or
+ * larger than 127.
+ * @return the byte v, unchanged
+ * @since 3.2
+ */
+ public static byte CONST_BYTE(final int v) {
+ if (v < Byte.MIN_VALUE || v > Byte.MAX_VALUE) {
+ throw new IllegalArgumentException("Supplied value must be a valid byte literal between -128 and 127: [" + v + "]");
+ }
+ return (byte) v;
+ }
+
+ /**
+ * This method returns the provided value unchanged.
+ * This can prevent javac from inlining a constant
+ * field, e.g.,
+ *
+ * <pre>
+ * public final static short MAGIC_SHORT = ObjectUtils.CONST_SHORT(127);
+ * </pre>
+ *
+ * This way any jars that refer to this field do not
+ * have to recompile themselves if the field's value
+ * changes at some future date.
+ *
+ * @param v the short literal (as an int) value to return
+ * @throws IllegalArgumentException if the value passed to v
+ * is larger than a short, that is, smaller than -32768 or
+ * larger than 32767.
+ * @return the byte v, unchanged
+ * @since 3.2
+ */
+ public static short CONST_SHORT(final int v) {
+ if (v < Short.MIN_VALUE || v > Short.MAX_VALUE) {
+ throw new IllegalArgumentException("Supplied value must be a valid byte literal between -32768 and 32767: [" + v + "]");
+ }
+ return (short) v;
+ }
+
+ /**
+ * Returns a default value if the object passed is {@code null}.
+ *
+ * <pre>
+ * ObjectUtils.defaultIfNull(null, null) = null
+ * ObjectUtils.defaultIfNull(null, "") = ""
+ * ObjectUtils.defaultIfNull(null, "zz") = "zz"
+ * ObjectUtils.defaultIfNull("abc", *) = "abc"
+ * ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
+ * </pre>
+ *
+ * @param <T> the type of the object
+ * @param object the {@link Object} to test, may be {@code null}
+ * @param defaultValue the default value to return, may be {@code null}
+ * @return {@code object} if it is not {@code null}, defaultValue otherwise
+ * TODO Rename to getIfNull in 4.0
+ */
+ public static <T> T defaultIfNull(final T object, final T defaultValue) {
+ return object != null ? object : defaultValue;
+ }
+
+ // Null-safe equals/hashCode
+ /**
+ * Compares two objects for equality, where either one or both
+ * objects may be {@code null}.
+ *
+ * <pre>
+ * ObjectUtils.equals(null, null) = true
+ * ObjectUtils.equals(null, "") = false
+ * ObjectUtils.equals("", null) = false
+ * ObjectUtils.equals("", "") = true
+ * ObjectUtils.equals(Boolean.TRUE, null) = false
+ * ObjectUtils.equals(Boolean.TRUE, "true") = false
+ * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE) = true
+ * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
+ * </pre>
+ *
+ * @param object1 the first object, may be {@code null}
+ * @param object2 the second object, may be {@code null}
+ * @return {@code true} if the values of both objects are the same
+ * @deprecated this method has been replaced by {@code java.util.Objects.equals(Object, Object)} in Java 7 and will
+ * be removed from future releases.
+ */
+ @Deprecated
+ public static boolean equals(final Object object1, final Object object2) {
+ return Objects.equals(object1, object2);
+ }
+
+ /**
+ * Returns the first value in the array which is not {@code null}.
+ * If all the values are {@code null} or the array is {@code null}
+ * or empty then {@code null} is returned.
+ *
+ * <pre>
+ * ObjectUtils.firstNonNull(null, null) = null
+ * ObjectUtils.firstNonNull(null, "") = ""
+ * ObjectUtils.firstNonNull(null, null, "") = ""
+ * ObjectUtils.firstNonNull(null, "zz") = "zz"
+ * ObjectUtils.firstNonNull("abc", *) = "abc"
+ * ObjectUtils.firstNonNull(null, "xyz", *) = "xyz"
+ * ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
+ * ObjectUtils.firstNonNull() = null
+ * </pre>
+ *
+ * @param <T> the component type of the array
+ * @param values the values to test, may be {@code null} or empty
+ * @return the first value from {@code values} which is not {@code null},
+ * or {@code null} if there are no non-null values
+ * @since 3.0
+ */
+ @SafeVarargs
+ public static <T> T firstNonNull(final T... values) {
+ return Streams.of(values).filter(Objects::nonNull).findFirst().orElse(null);
+ }
+
+ /**
+ * Delegates to {@link Object#getClass()} using generics.
+ *
+ * @param <T> The argument type or null.
+ * @param object The argument.
+ * @return The argument Class or null.
+ * @since 3.13.0
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> getClass(final T object) {
+ return object == null ? null : (Class<T>) object.getClass();
+ }
+
+ /**
+ * Executes the given suppliers in order and returns the first return
+ * value where a value other than {@code null} is returned.
+ * Once a non-{@code null} value is obtained, all following suppliers are
+ * not executed anymore.
+ * If all the return values are {@code null} or no suppliers are provided
+ * then {@code null} is returned.
+ *
+ * <pre>
+ * ObjectUtils.firstNonNullLazy(null, () -&gt; null) = null
+ * ObjectUtils.firstNonNullLazy(() -&gt; null, () -&gt; "") = ""
+ * ObjectUtils.firstNonNullLazy(() -&gt; "", () -&gt; throw new IllegalStateException()) = ""
+ * ObjectUtils.firstNonNullLazy(() -&gt; null, () -&gt; "zz) = "zz"
+ * ObjectUtils.firstNonNullLazy() = null
+ * </pre>
+ *
+ * @param <T> the type of the return values
+ * @param suppliers the suppliers returning the values to test.
+ * {@code null} values are ignored.
+ * Suppliers may return {@code null} or a value of type @{code T}
+ * @return the first return value from {@code suppliers} which is not {@code null},
+ * or {@code null} if there are no non-null values
+ * @since 3.10
+ */
+ @SafeVarargs
+ public static <T> T getFirstNonNull(final Supplier<T>... suppliers) {
+ return Streams.of(suppliers).map(s -> s != null ? s.get() : null).filter(Objects::nonNull).findFirst().orElse(null);
+ }
+
+ /**
+ * Returns the given {@code object} is it is non-null, otherwise returns the Supplier's {@link Supplier#get()}
+ * value.
+ *
+ * <p>
+ * The caller responsible for thread-safety and exception handling of default value supplier.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.getIfNull(null, () -&gt; null) = null
+ * ObjectUtils.getIfNull(null, null) = null
+ * ObjectUtils.getIfNull(null, () -&gt; "") = ""
+ * ObjectUtils.getIfNull(null, () -&gt; "zz") = "zz"
+ * ObjectUtils.getIfNull("abc", *) = "abc"
+ * ObjectUtils.getIfNull(Boolean.TRUE, *) = Boolean.TRUE
+ * </pre>
+ *
+ * @param <T> the type of the object
+ * @param object the {@link Object} to test, may be {@code null}
+ * @param defaultSupplier the default value to return, may be {@code null}
+ * @return {@code object} if it is not {@code null}, {@code defaultValueSupplier.get()} otherwise
+ * @since 3.10
+ */
+ public static <T> T getIfNull(final T object, final Supplier<T> defaultSupplier) {
+ return object != null ? object : Suppliers.get(defaultSupplier);
+ }
+
+ /**
+ * Gets the hash code of an object returning zero when the
+ * object is {@code null}.
+ *
+ * <pre>
+ * ObjectUtils.hashCode(null) = 0
+ * ObjectUtils.hashCode(obj) = obj.hashCode()
+ * </pre>
+ *
+ * @param obj the object to obtain the hash code of, may be {@code null}
+ * @return the hash code of the object, or zero if null
+ * @since 2.1
+ * @deprecated this method has been replaced by {@code java.util.Objects.hashCode(Object)} in Java 7 and will be
+ * removed in future releases
+ */
+ @Deprecated
+ public static int hashCode(final Object obj) {
+ // hashCode(Object) for performance vs. hashCodeMulti(Object[]), as hash code is often critical
+ return Objects.hashCode(obj);
+ }
+
+ /**
+ * Returns the hex hash code for the given object per {@link Objects#hashCode(Object)}.
+ * <p>
+ * Short hand for {@code Integer.toHexString(Objects.hashCode(object))}.
+ * </p>
+ *
+ * @param object object for which the hashCode is to be calculated
+ * @return Hash code in hexadecimal format.
+ * @since 3.13.0
+ */
+ public static String hashCodeHex(final Object object) {
+ return Integer.toHexString(Objects.hashCode(object));
+ }
+
+
+ /**
+ * Gets the hash code for multiple objects.
+ *
+ * <p>This allows a hash code to be rapidly calculated for a number of objects.
+ * The hash code for a single object is the <em>not</em> same as {@link #hashCode(Object)}.
+ * The hash code for multiple objects is the same as that calculated by an
+ * {@link ArrayList} containing the specified objects.</p>
+ *
+ * <pre>
+ * ObjectUtils.hashCodeMulti() = 1
+ * ObjectUtils.hashCodeMulti((Object[]) null) = 1
+ * ObjectUtils.hashCodeMulti(a) = 31 + a.hashCode()
+ * ObjectUtils.hashCodeMulti(a,b) = (31 + a.hashCode()) * 31 + b.hashCode()
+ * ObjectUtils.hashCodeMulti(a,b,c) = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
+ * </pre>
+ *
+ * @param objects the objects to obtain the hash code of, may be {@code null}
+ * @return the hash code of the objects, or zero if null
+ * @since 3.0
+ * @deprecated this method has been replaced by {@code java.util.Objects.hash(Object...)} in Java 7 and will be
+ * removed in future releases.
+ */
+ @Deprecated
+ public static int hashCodeMulti(final Object... objects) {
+ int hash = 1;
+ if (objects != null) {
+ for (final Object object : objects) {
+ final int tmpHash = Objects.hashCode(object);
+ hash = hash * 31 + tmpHash;
+ }
+ }
+ return hash;
+ }
+
+ /**
+ * Returns the hex hash code for the given object per {@link System#identityHashCode(Object)}.
+ * <p>
+ * Short hand for {@code Integer.toHexString(System.identityHashCode(object))}.
+ * </p>
+ *
+ * @param object object for which the hashCode is to be calculated
+ * @return Hash code in hexadecimal format.
+ * @since 3.13.0
+ */
+ public static String identityHashCodeHex(final Object object) {
+ return Integer.toHexString(System.identityHashCode(object));
+ }
+
+ /**
+ * Appends the toString that would be produced by {@link Object}
+ * if a class did not override toString itself. {@code null}
+ * will throw a NullPointerException for either of the two parameters.
+ *
+ * <pre>
+ * ObjectUtils.identityToString(appendable, "") = appendable.append("java.lang.String@1e23")
+ * ObjectUtils.identityToString(appendable, Boolean.TRUE) = appendable.append("java.lang.Boolean@7fa")
+ * ObjectUtils.identityToString(appendable, Boolean.TRUE) = appendable.append("java.lang.Boolean@7fa")
+ * </pre>
+ *
+ * @param appendable the appendable to append to
+ * @param object the object to create a toString for
+ * @throws IOException if an I/O error occurs.
+ * @since 3.2
+ */
+ public static void identityToString(final Appendable appendable, final Object object) throws IOException {
+ Objects.requireNonNull(object, "object");
+ appendable.append(object.getClass().getName())
+ .append(AT_SIGN)
+ .append(identityHashCodeHex(object));
+ }
+
+ /**
+ * Gets the toString that would be produced by {@link Object}
+ * if a class did not override toString itself. {@code null}
+ * will return {@code null}.
+ *
+ * <pre>
+ * ObjectUtils.identityToString(null) = null
+ * ObjectUtils.identityToString("") = "java.lang.String@1e23"
+ * ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
+ * </pre>
+ *
+ * @param object the object to create a toString for, may be
+ * {@code null}
+ * @return the default toString text, or {@code null} if
+ * {@code null} passed in
+ */
+ public static String identityToString(final Object object) {
+ if (object == null) {
+ return null;
+ }
+ final String name = object.getClass().getName();
+ final String hexString = identityHashCodeHex(object);
+ final StringBuilder builder = new StringBuilder(name.length() + 1 + hexString.length());
+ // @formatter:off
+ builder.append(name)
+ .append(AT_SIGN)
+ .append(hexString);
+ // @formatter:on
+ return builder.toString();
+ }
+
+ /**
+ * Appends the toString that would be produced by {@link Object}
+ * if a class did not override toString itself. {@code null}
+ * will throw a NullPointerException for either of the two parameters.
+ *
+ * <pre>
+ * ObjectUtils.identityToString(builder, "") = builder.append("java.lang.String@1e23")
+ * ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
+ * ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
+ * </pre>
+ *
+ * @param builder the builder to append to
+ * @param object the object to create a toString for
+ * @since 3.2
+ * @deprecated as of 3.6, because StrBuilder was moved to commons-text,
+ * use one of the other {@code identityToString} methods instead
+ */
+ @Deprecated
+ public static void identityToString(final StrBuilder builder, final Object object) {
+ Objects.requireNonNull(object, "object");
+ final String name = object.getClass().getName();
+ final String hexString = identityHashCodeHex(object);
+ builder.ensureCapacity(builder.length() + name.length() + 1 + hexString.length());
+ builder.append(name)
+ .append(AT_SIGN)
+ .append(hexString);
+ }
+
+ /**
+ * Appends the toString that would be produced by {@link Object}
+ * if a class did not override toString itself. {@code null}
+ * will throw a NullPointerException for either of the two parameters.
+ *
+ * <pre>
+ * ObjectUtils.identityToString(buf, "") = buf.append("java.lang.String@1e23")
+ * ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
+ * ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
+ * </pre>
+ *
+ * @param buffer the buffer to append to
+ * @param object the object to create a toString for
+ * @since 2.4
+ */
+ public static void identityToString(final StringBuffer buffer, final Object object) {
+ Objects.requireNonNull(object, "object");
+ final String name = object.getClass().getName();
+ final String hexString = identityHashCodeHex(object);
+ buffer.ensureCapacity(buffer.length() + name.length() + 1 + hexString.length());
+ buffer.append(name)
+ .append(AT_SIGN)
+ .append(hexString);
+ }
+
+ /**
+ * Appends the toString that would be produced by {@link Object}
+ * if a class did not override toString itself. {@code null}
+ * will throw a NullPointerException for either of the two parameters.
+ *
+ * <pre>
+ * ObjectUtils.identityToString(builder, "") = builder.append("java.lang.String@1e23")
+ * ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
+ * ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
+ * </pre>
+ *
+ * @param builder the builder to append to
+ * @param object the object to create a toString for
+ * @since 3.2
+ */
+ public static void identityToString(final StringBuilder builder, final Object object) {
+ Objects.requireNonNull(object, "object");
+ final String name = object.getClass().getName();
+ final String hexString = identityHashCodeHex(object);
+ builder.ensureCapacity(builder.length() + name.length() + 1 + hexString.length());
+ builder.append(name)
+ .append(AT_SIGN)
+ .append(hexString);
+ }
+
+
+ // Constants (LANG-816):
+ /*
+ These methods ensure constants are not inlined by javac.
+ For example, typically a developer might declare a constant like so:
+
+ public final static int MAGIC_NUMBER = 5;
+
+ Should a different jar file refer to this, and the MAGIC_NUMBER
+ is changed a later date (e.g., MAGIC_NUMBER = 6), the different jar
+ file will need to recompile itself. This is because javac
+ typically inlines the primitive or String constant directly into
+ the bytecode, and removes the reference to the MAGIC_NUMBER field.
+
+ To help the other jar (so that it does not need to recompile
+ when constants are changed) the original developer can declare
+ their constant using one of the CONST() utility methods, instead:
+
+ public final static int MAGIC_NUMBER = CONST(5);
+ */
+
+ /**
+ * Checks, whether the given object is an Object array or a primitive array in a null-safe manner.
+ *
+ * <p>
+ * A {@code null} {@code object} Object will return {@code false}.
+ * </p>
+ *
+ * <pre>
+ * ObjectUtils.isArray(null) = false
+ * ObjectUtils.isArray("") = false
+ * ObjectUtils.isArray("ab") = false
+ * ObjectUtils.isArray(new int[]{}) = true
+ * ObjectUtils.isArray(new int[]{1,2,3}) = true
+ * ObjectUtils.isArray(1234) = false
+ * </pre>
+ *
+ * @param object the object to check, may be {@code null}
+ * @return {@code true} if the object is an {@code array}, {@code false} otherwise
+ * @since 3.13.0
+ */
+ public static boolean isArray(final Object object) {
+ return object != null && object.getClass().isArray();
+ }
+
+ /**
+ * Checks if an Object is empty or null.
+ *
+ * The following types are supported:
+ * <ul>
+ * <li>{@link CharSequence}: Considered empty if its length is zero.</li>
+ * <li>{@link Array}: Considered empty if its length is zero.</li>
+ * <li>{@link Collection}: Considered empty if it has zero elements.</li>
+ * <li>{@link Map}: Considered empty if it has zero key-value mappings.</li>
+ * <li>{@link Optional}: Considered empty if {@link Optional#isPresent} returns false, regardless of the "emptiness" of the contents.</li>
+ * </ul>
+ *
+ * <pre>
+ * ObjectUtils.isEmpty(null) = true
+ * ObjectUtils.isEmpty("") = true
+ * ObjectUtils.isEmpty("ab") = false
+ * ObjectUtils.isEmpty(new int[]{}) = true
+ * ObjectUtils.isEmpty(new int[]{1,2,3}) = false
+ * ObjectUtils.isEmpty(1234) = false
+ * ObjectUtils.isEmpty(1234) = false
+ * ObjectUtils.isEmpty(Optional.of("")) = false
+ * ObjectUtils.isEmpty(Optional.empty()) = true
+ * </pre>
+ *
+ * @param object the {@link Object} to test, may be {@code null}
+ * @return {@code true} if the object has a supported type and is empty or null,
+ * {@code false} otherwise
+ * @since 3.9
+ */
+ public static boolean isEmpty(final Object object) {
+ if (object == null) {
+ return true;
+ }
+ if (object instanceof CharSequence) {
+ return ((CharSequence) object).length() == 0;
+ }
+ if (isArray(object)) {
+ return Array.getLength(object) == 0;
+ }
+ if (object instanceof Collection<?>) {
+ return ((Collection<?>) object).isEmpty();
+ }
+ if (object instanceof Map<?, ?>) {
+ return ((Map<?, ?>) object).isEmpty();
+ }
+ if (object instanceof Optional<?>) {
+ // TODO Java 11 Use Optional#isEmpty()
+ return !((Optional<?>) object).isPresent();
+ }
+ return false;
+ }
+
+ /**
+ * Checks if an Object is not empty and not null.
+ *
+ * The following types are supported:
+ * <ul>
+ * <li>{@link CharSequence}: Considered empty if its length is zero.</li>
+ * <li>{@link Array}: Considered empty if its length is zero.</li>
+ * <li>{@link Collection}: Considered empty if it has zero elements.</li>
+ * <li>{@link Map}: Considered empty if it has zero key-value mappings.</li>
+ * <li>{@link Optional}: Considered empty if {@link Optional#isPresent} returns false, regardless of the "emptiness" of the contents.</li>
+ * </ul>
+ *
+ * <pre>
+ * ObjectUtils.isNotEmpty(null) = false
+ * ObjectUtils.isNotEmpty("") = false
+ * ObjectUtils.isNotEmpty("ab") = true
+ * ObjectUtils.isNotEmpty(new int[]{}) = false
+ * ObjectUtils.isNotEmpty(new int[]{1,2,3}) = true
+ * ObjectUtils.isNotEmpty(1234) = true
+ * ObjectUtils.isNotEmpty(Optional.of("")) = true
+ * ObjectUtils.isNotEmpty(Optional.empty()) = false
+ * </pre>
+ *
+ * @param object the {@link Object} to test, may be {@code null}
+ * @return {@code true} if the object has an unsupported type or is not empty
+ * and not null, {@code false} otherwise
+ * @since 3.9
+ */
+ public static boolean isNotEmpty(final Object object) {
+ return !isEmpty(object);
+ }
+
+ /**
+ * Null safe comparison of Comparables.
+ * <p>TODO Move to ComparableUtils.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param values the set of comparable values, may be null
+ * @return
+ * <ul>
+ * <li>If any objects are non-null and unequal, the greater object.
+ * <li>If all objects are non-null and equal, the first.
+ * <li>If any of the comparables are null, the greater of the non-null objects.
+ * <li>If all the comparables are null, null is returned.
+ * </ul>
+ */
+ @SafeVarargs
+ public static <T extends Comparable<? super T>> T max(final T... values) {
+ T result = null;
+ if (values != null) {
+ for (final T value : values) {
+ if (compare(value, result, false) > 0) {
+ result = value;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Find the "best guess" middle value among comparables. If there is an even
+ * number of total values, the lower of the two middle values will be returned.
+ * @param <T> type of values processed by this method
+ * @param comparator to use for comparisons
+ * @param items to compare
+ * @return T at middle position
+ * @throws NullPointerException if items or comparator is {@code null}
+ * @throws IllegalArgumentException if items is empty or contains {@code null} values
+ * @since 3.0.1
+ */
+ @SafeVarargs
+ public static <T> T median(final Comparator<T> comparator, final T... items) {
+ Validate.notEmpty(items, "null/empty items");
+ Validate.noNullElements(items);
+ Objects.requireNonNull(comparator, "comparator");
+ final TreeSet<T> treeSet = new TreeSet<>(comparator);
+ Collections.addAll(treeSet, items);
+ @SuppressWarnings("unchecked") //we know all items added were T instances
+ final T result = (T) treeSet.toArray()[(treeSet.size() - 1) / 2];
+ return result;
+ }
+
+ /**
+ * Find the "best guess" middle value among comparables. If there is an even
+ * number of total values, the lower of the two middle values will be returned.
+ * @param <T> type of values processed by this method
+ * @param items to compare
+ * @return T at middle position
+ * @throws NullPointerException if items is {@code null}
+ * @throws IllegalArgumentException if items is empty or contains {@code null} values
+ * @since 3.0.1
+ */
+ @SafeVarargs
+ public static <T extends Comparable<? super T>> T median(final T... items) {
+ Validate.notEmpty(items);
+ Validate.noNullElements(items);
+ final TreeSet<T> sort = new TreeSet<>();
+ Collections.addAll(sort, items);
+ @SuppressWarnings("unchecked") //we know all items added were T instances
+ final T result = (T) sort.toArray()[(sort.size() - 1) / 2];
+ return result;
+ }
+
+ /**
+ * Null safe comparison of Comparables.
+ * <p>TODO Move to ComparableUtils.</p>
+ *
+ * @param <T> type of the values processed by this method
+ * @param values the set of comparable values, may be null
+ * @return
+ * <ul>
+ * <li>If any objects are non-null and unequal, the lesser object.
+ * <li>If all objects are non-null and equal, the first.
+ * <li>If any of the comparables are null, the lesser of the non-null objects.
+ * <li>If all the comparables are null, null is returned.
+ * </ul>
+ */
+ @SafeVarargs
+ public static <T extends Comparable<? super T>> T min(final T... values) {
+ T result = null;
+ if (values != null) {
+ for (final T value : values) {
+ if (compare(value, result, true) < 0) {
+ result = value;
+ }
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Find the most frequently occurring item.
+ *
+ * @param <T> type of values processed by this method
+ * @param items to check
+ * @return most populous T, {@code null} if non-unique or no items supplied
+ * @since 3.0.1
+ */
+ @SafeVarargs
+ public static <T> T mode(final T... items) {
+ if (ArrayUtils.isNotEmpty(items)) {
+ final HashMap<T, MutableInt> occurrences = new HashMap<>(items.length);
+ for (final T t : items) {
+ final MutableInt count = occurrences.get(t);
+ if (count == null) {
+ occurrences.put(t, new MutableInt(1));
+ } else {
+ count.increment();
+ }
+ }
+ T result = null;
+ int max = 0;
+ for (final Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
+ final int cmp = e.getValue().intValue();
+ if (cmp == max) {
+ result = null;
+ } else if (cmp > max) {
+ max = cmp;
+ result = e.getKey();
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Compares two objects for inequality, where either one or both
+ * objects may be {@code null}.
+ *
+ * <pre>
+ * ObjectUtils.notEqual(null, null) = false
+ * ObjectUtils.notEqual(null, "") = true
+ * ObjectUtils.notEqual("", null) = true
+ * ObjectUtils.notEqual("", "") = false
+ * ObjectUtils.notEqual(Boolean.TRUE, null) = true
+ * ObjectUtils.notEqual(Boolean.TRUE, "true") = true
+ * ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE) = false
+ * ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
+ * </pre>
+ *
+ * @param object1 the first object, may be {@code null}
+ * @param object2 the second object, may be {@code null}
+ * @return {@code false} if the values of both objects are the same
+ */
+ public static boolean notEqual(final Object object1, final Object object2) {
+ return !Objects.equals(object1, object2);
+ }
+
+ /**
+ * Checks that the specified object reference is not {@code null} or empty per {@link #isEmpty(Object)}. Use this
+ * method for validation, for example:
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * public Foo(Bar bar) {
+ * this.bar = Objects.requireNonEmpty(bar);
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * @param <T> the type of the reference.
+ * @param obj the object reference to check for nullity.
+ * @return {@code obj} if not {@code null}.
+ * @throws NullPointerException if {@code obj} is {@code null}.
+ * @throws IllegalArgumentException if {@code obj} is empty per {@link #isEmpty(Object)}.
+ * @see #isEmpty(Object)
+ * @since 3.12.0
+ */
+ public static <T> T requireNonEmpty(final T obj) {
+ return requireNonEmpty(obj, "object");
+ }
+
+ /**
+ * Checks that the specified object reference is not {@code null} or empty per {@link #isEmpty(Object)}. Use this
+ * method for validation, for example:
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * public Foo(Bar bar) {
+ * this.bar = Objects.requireNonEmpty(bar, "bar");
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * @param <T> the type of the reference.
+ * @param obj the object reference to check for nullity.
+ * @param message the exception message.
+ * @return {@code obj} if not {@code null}.
+ * @throws NullPointerException if {@code obj} is {@code null}.
+ * @throws IllegalArgumentException if {@code obj} is empty per {@link #isEmpty(Object)}.
+ * @see #isEmpty(Object)
+ * @since 3.12.0
+ */
+ public static <T> T requireNonEmpty(final T obj, final String message) {
+ // check for null first to give the most precise exception.
+ Objects.requireNonNull(obj, message);
+ if (isEmpty(obj)) {
+ throw new IllegalArgumentException(message);
+ }
+ return obj;
+ }
+
+ /**
+ * Gets the {@code toString} of an {@link Object} returning
+ * an empty string ("") if {@code null} input.
+ *
+ * <pre>
+ * ObjectUtils.toString(null) = ""
+ * ObjectUtils.toString("") = ""
+ * ObjectUtils.toString("bat") = "bat"
+ * ObjectUtils.toString(Boolean.TRUE) = "true"
+ * </pre>
+ *
+ * @see StringUtils#defaultString(String)
+ * @see String#valueOf(Object)
+ * @param obj the Object to {@code toString}, may be null
+ * @return the passed in Object's toString, or {@code ""} if {@code null} input
+ * @since 2.0
+ * @deprecated this method has been replaced by {@code java.util.Objects.toString(Object)} in Java 7 and will be
+ * removed in future releases. Note however that said method will return "null" for null references, while this
+ * method returns an empty String. To preserve behavior use {@code java.util.Objects.toString(myObject, "")}
+ */
+ @Deprecated
+ public static String toString(final Object obj) {
+ return obj == null ? StringUtils.EMPTY : obj.toString();
+ }
+
+ /**
+ * Gets the {@code toString} of an {@link Object} returning
+ * a specified text if {@code null} input.
+ *
+ * <pre>
+ * ObjectUtils.toString(null, null) = null
+ * ObjectUtils.toString(null, "null") = "null"
+ * ObjectUtils.toString("", "null") = ""
+ * ObjectUtils.toString("bat", "null") = "bat"
+ * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
+ * </pre>
+ *
+ * @see StringUtils#defaultString(String,String)
+ * @see String#valueOf(Object)
+ * @param obj the Object to {@code toString}, may be null
+ * @param nullStr the String to return if {@code null} input, may be null
+ * @return the passed in Object's toString, or {@code nullStr} if {@code null} input
+ * @since 2.0
+ * @deprecated this method has been replaced by {@code java.util.Objects.toString(Object, String)} in Java 7 and
+ * will be removed in future releases.
+ */
+ @Deprecated
+ public static String toString(final Object obj, final String nullStr) {
+ return obj == null ? nullStr : obj.toString();
+ }
+
+ /**
+ * Gets the {@code toString} of an {@link Object} returning
+ * a specified text if {@code null} input.
+ *
+ * <pre>
+ * ObjectUtils.toString(obj, () -&gt; expensive())
+ * </pre>
+ * <pre>
+ * ObjectUtils.toString(null, () -&gt; expensive()) = result of expensive()
+ * ObjectUtils.toString(null, () -&gt; expensive()) = result of expensive()
+ * ObjectUtils.toString("", () -&gt; expensive()) = ""
+ * ObjectUtils.toString("bat", () -&gt; expensive()) = "bat"
+ * ObjectUtils.toString(Boolean.TRUE, () -&gt; expensive()) = "true"
+ * </pre>
+ *
+ * @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.11
+ */
+ public static String toString(final Object obj, final Supplier<String> supplier) {
+ return obj == null ? Suppliers.get(supplier) : obj.toString();
+ }
+
+ /**
+ * Calls {@link Object#wait(long, int)} for the given Duration.
+ *
+ * @param obj The receiver of the wait call.
+ * @param duration How long to wait.
+ * @throws IllegalArgumentException if the timeout duration is negative.
+ * @throws IllegalMonitorStateException if the current thread is not the owner of the {@code obj}'s monitor.
+ * @throws InterruptedException if any thread interrupted the current thread before or while the current thread was
+ * waiting for a notification. The <em>interrupted status</em> of the current thread is cleared when this
+ * exception is thrown.
+ * @see Object#wait(long, int)
+ * @since 3.12.0
+ */
+ public static void wait(final Object obj, final Duration duration) throws InterruptedException {
+ DurationUtils.accept(obj::wait, DurationUtils.zeroIfNull(duration));
+ }
+
+ /**
+ * {@link ObjectUtils} instances should NOT be constructed in
+ * standard programming. Instead, the static methods on the class should
+ * be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public ObjectUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
new file mode 100644
index 000000000..798542034
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
@@ -0,0 +1,475 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Generates random {@link String}s.
+ *
+ * <p><b>Caveat: Instances of {@link Random}, upon which the implementation of this
+ * class relies, are not cryptographically secure.</b></p>
+ *
+ * <p>RandomStringUtils is intended for simple use cases. For more advanced
+ * use cases consider using Apache Commons Text's
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/RandomStringGenerator.html">
+ * RandomStringGenerator</a> instead.</p>
+ *
+ * <p>The Apache Commons project provides
+ * <a href="https://commons.apache.org/proper/commons-rng/">Commons RNG</a> dedicated to pseudo-random number generation, that may be
+ * a better choice for applications with more stringent requirements
+ * (performance and/or correctness).</p>
+ *
+ * <p>Note that <em>private high surrogate</em> characters are ignored.
+ * These are Unicode characters that fall between the values 56192 (db80)
+ * and 56319 (dbff) as we don't know how to handle them.
+ * High and low surrogates are correctly dealt with - that is if a
+ * high surrogate is randomly chosen, 55296 (d800) to 56191 (db7f)
+ * then it is followed by a low surrogate. If a low surrogate is chosen,
+ * 56320 (dc00) to 57343 (dfff) then it is placed after a randomly
+ * chosen high surrogate.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ */
+public class RandomStringUtils {
+
+ private static ThreadLocalRandom random() {
+ return ThreadLocalRandom.current();
+ }
+
+ // Random
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of all characters.</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ */
+ public static String random(final int count) {
+ return random(count, false, false);
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of alpha-numeric
+ * characters as indicated by the arguments.</p>
+ *
+ * @param count the length of random string to create
+ * @param letters if {@code true}, generated string may include
+ * alphabetic characters
+ * @param numbers if {@code true}, generated string may include
+ * numeric characters
+ * @return the random string
+ */
+ public static String random(final int count, final boolean letters, final boolean numbers) {
+ return random(count, 0, 0, letters, numbers);
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of characters specified.</p>
+ *
+ * @param count the length of random string to create
+ * @param chars the character array containing the set of characters to use,
+ * may be null
+ * @return the random string
+ * @throws IllegalArgumentException if {@code count} &lt; 0.
+ */
+ public static String random(final int count, final char... chars) {
+ if (chars == null) {
+ return random(count, 0, 0, false, false, null, random());
+ }
+ return random(count, 0, chars.length, false, false, chars, random());
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of alpha-numeric
+ * characters as indicated by the arguments.</p>
+ *
+ * @param count the length of random string to create
+ * @param start the position in set of chars to start at
+ * @param end the position in set of chars to end before
+ * @param letters if {@code true}, generated string may include
+ * alphabetic characters
+ * @param numbers if {@code true}, generated string may include
+ * numeric characters
+ * @return the random string
+ */
+ public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers) {
+ return random(count, start, end, letters, numbers, null, random());
+ }
+
+ /**
+ * Creates a random string based on a variety of options, using
+ * default source of randomness.
+ *
+ * <p>This method has exactly the same semantics as
+ * {@link #random(int,int,int,boolean,boolean,char[],Random)}, but
+ * instead of using an externally supplied source of randomness, it uses
+ * the internal static {@link Random} instance.</p>
+ *
+ * @param count the length of random string to create
+ * @param start the position in set of chars to start at
+ * @param end the position in set of chars to end before
+ * @param letters if {@code true}, generated string may include
+ * alphabetic characters
+ * @param numbers if {@code true}, generated string may include
+ * numeric characters
+ * @param chars the set of chars to choose randoms from.
+ * If {@code null}, then it will use the set of all chars.
+ * @return the random string
+ * @throws ArrayIndexOutOfBoundsException if there are not
+ * {@code (end - start) + 1} characters in the set array.
+ */
+ public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers, final char... chars) {
+ return random(count, start, end, letters, numbers, chars, random());
+ }
+
+ /**
+ * Creates a random string based on a variety of options, using
+ * supplied source of randomness.
+ *
+ * <p>If start and end are both {@code 0}, start and end are set
+ * to {@code ' '} and {@code 'z'}, the ASCII printable
+ * characters, will be used, unless letters and numbers are both
+ * {@code false}, in which case, start and end are set to
+ * {@code 0} and {@link Character#MAX_CODE_POINT}.
+ *
+ * <p>If set is not {@code null}, characters between start and
+ * end are chosen.</p>
+ *
+ * <p>This method accepts a user-supplied {@link Random}
+ * instance to use as a source of randomness. By seeding a single
+ * {@link Random} instance with a fixed seed and using it for each call,
+ * the same random sequence of strings can be generated repeatedly
+ * and predictably.</p>
+ *
+ * @param count the length of random string to create
+ * @param start the position in set of chars to start at (inclusive)
+ * @param end the position in set of chars to end before (exclusive)
+ * @param letters if {@code true}, generated string may include
+ * alphabetic characters
+ * @param numbers if {@code true}, generated string may include
+ * numeric characters
+ * @param chars the set of chars to choose randoms from, must not be empty.
+ * If {@code null}, then it will use the set of all chars.
+ * @param random a source of randomness.
+ * @return the random string
+ * @throws ArrayIndexOutOfBoundsException if there are not
+ * {@code (end - start) + 1} characters in the set array.
+ * @throws IllegalArgumentException if {@code count} &lt; 0 or the provided chars array is empty.
+ * @since 2.0
+ */
+ public static String random(int count, int start, int end, final boolean letters, final boolean numbers,
+ final char[] chars, final Random random) {
+ if (count == 0) {
+ return StringUtils.EMPTY;
+ }
+ if (count < 0) {
+ throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
+ }
+ if (chars != null && chars.length == 0) {
+ throw new IllegalArgumentException("The chars array must not be empty");
+ }
+
+ if (start == 0 && end == 0) {
+ if (chars != null) {
+ end = chars.length;
+ } else if (!letters && !numbers) {
+ end = Character.MAX_CODE_POINT;
+ } else {
+ end = 'z' + 1;
+ start = ' ';
+ }
+ } else if (end <= start) {
+ throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")");
+ }
+
+ final int zero_digit_ascii = 48;
+ final int first_letter_ascii = 65;
+
+ if (chars == null && (numbers && end <= zero_digit_ascii
+ || letters && end <= first_letter_ascii)) {
+ throw new IllegalArgumentException("Parameter end (" + end + ") must be greater then (" + zero_digit_ascii + ") for generating digits " +
+ "or greater then (" + first_letter_ascii + ") for generating letters.");
+ }
+
+ final StringBuilder builder = new StringBuilder(count);
+ final int gap = end - start;
+
+ while (count-- != 0) {
+ final int codePoint;
+ if (chars == null) {
+ codePoint = random.nextInt(gap) + start;
+
+ switch (Character.getType(codePoint)) {
+ case Character.UNASSIGNED:
+ case Character.PRIVATE_USE:
+ case Character.SURROGATE:
+ count++;
+ continue;
+ }
+
+ } else {
+ codePoint = chars[random.nextInt(gap) + start];
+ }
+
+ final int numberOfChars = Character.charCount(codePoint);
+ if (count == 0 && numberOfChars > 1) {
+ count++;
+ continue;
+ }
+
+ if (letters && Character.isLetter(codePoint)
+ || numbers && Character.isDigit(codePoint)
+ || !letters && !numbers) {
+ builder.appendCodePoint(codePoint);
+
+ if (numberOfChars == 2) {
+ count--;
+ }
+
+ } else {
+ count++;
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of characters
+ * specified by the string, must not be empty.
+ * If null, the set of all characters is used.</p>
+ *
+ * @param count the length of random string to create
+ * @param chars the String containing the set of characters to use,
+ * may be null, but must not be empty
+ * @return the random string
+ * @throws IllegalArgumentException if {@code count} &lt; 0 or the string is empty.
+ */
+ public static String random(final int count, final String chars) {
+ if (chars == null) {
+ return random(count, 0, 0, false, false, null, random());
+ }
+ return random(count, chars.toCharArray());
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of Latin alphabetic
+ * characters (a-z, A-Z).</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ */
+ public static String randomAlphabetic(final int count) {
+ return random(count, true, false);
+ }
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z).</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomAlphabetic(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomAlphabetic(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of Latin alphabetic
+ * characters (a-z, A-Z) and the digits 0-9.</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ */
+ public static String randomAlphanumeric(final int count) {
+ return random(count, true, true);
+ }
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of Latin alphabetic
+ * characters (a-z, A-Z) and the digits 0-9.</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomAlphanumeric(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomAlphanumeric(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of characters whose
+ * ASCII value is between {@code 32} and {@code 126} (inclusive).</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ */
+ public static String randomAscii(final int count) {
+ return random(count, 32, 127, false, false);
+ }
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of characters whose
+ * ASCII value is between {@code 32} and {@code 126} (inclusive).</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomAscii(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomAscii(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters specified.
+ *
+ * <p>Characters will be chosen from the set of characters which match the POSIX [:graph:]
+ * regular expression character class. This class contains all visible ASCII characters
+ * (i.e. anything except spaces and control characters).</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomGraph(final int count) {
+ return random(count, 33, 126, false, false);
+ }
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of \p{Graph} characters.</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomGraph(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomGraph(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters
+ * specified.
+ *
+ * <p>Characters will be chosen from the set of numeric
+ * characters.</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ */
+ public static String randomNumeric(final int count) {
+ return random(count, false, true);
+ }
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of \p{Digit} characters.</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomNumeric(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomNumeric(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * Creates a random string whose length is the number of characters specified.
+ *
+ * <p>Characters will be chosen from the set of characters which match the POSIX [:print:]
+ * regular expression character class. This class includes all visible ASCII characters and spaces
+ * (i.e. anything except control characters).</p>
+ *
+ * @param count the length of random string to create
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomPrint(final int count) {
+ return random(count, 32, 126, false, false);
+ }
+
+
+ /**
+ * Creates a random string whose length is between the inclusive minimum and
+ * the exclusive maximum.
+ *
+ * <p>Characters will be chosen from the set of \p{Print} characters.</p>
+ *
+ * @param minLengthInclusive the inclusive minimum length of the string to generate
+ * @param maxLengthExclusive the exclusive maximum length of the string to generate
+ * @return the random string
+ * @since 3.5
+ */
+ public static String randomPrint(final int minLengthInclusive, final int maxLengthExclusive) {
+ return randomPrint(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive));
+ }
+
+ /**
+ * {@link RandomStringUtils} instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * {@code RandomStringUtils.random(5);}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public RandomStringUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/RandomUtils.java b/src/main/java/org/apache/commons/lang3/RandomUtils.java
new file mode 100644
index 000000000..95349685d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/RandomUtils.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Utility library that supplements the standard {@link Random} class.
+ *
+ * <p>Caveat: Instances of {@link Random} are not cryptographically secure.</p>
+ *
+ * <p>Please note that the Apache Commons project provides a component
+ * dedicated to pseudo-random number generation, namely
+ * <a href="https://commons.apache.org/proper/commons-rng/">Commons RNG</a>, that may be
+ * a better choice for applications with more stringent requirements
+ * (performance and/or correctness).</p>
+ *
+ * @deprecated Use Apache Commons RNG's optimized <a href="https://commons.apache.org/proper/commons-rng/commons-rng-client-api/apidocs/org/apache/commons/rng/UniformRandomProvider.html">UniformRandomProvider</a>
+ * @since 3.3
+ */
+@Deprecated
+public class RandomUtils {
+
+ /**
+ * Generates a random boolean value.
+ *
+ * @return the random boolean
+ * @since 3.5
+ */
+ public static boolean nextBoolean() {
+ return random().nextBoolean();
+ }
+
+ /**
+ * Generates an array of random bytes.
+ *
+ * @param count
+ * the size of the returned array
+ * @return the random byte array
+ * @throws IllegalArgumentException if {@code count} is negative
+ */
+ public static byte[] nextBytes(final int count) {
+ Validate.isTrue(count >= 0, "Count cannot be negative.");
+
+ final byte[] result = new byte[count];
+ random().nextBytes(result);
+ return result;
+ }
+
+ /**
+ * Generates a random double within 0 - Double.MAX_VALUE.
+ *
+ * @return the random double
+ * @see #nextDouble(double, double)
+ * @since 3.5
+ */
+ public static double nextDouble() {
+ return nextDouble(0, Double.MAX_VALUE);
+ }
+
+ /**
+ * Generates a random double within the specified range.
+ *
+ * @param startInclusive
+ * the smallest value that can be returned, must be non-negative
+ * @param endExclusive
+ * the upper bound (not included)
+ * @throws IllegalArgumentException
+ * if {@code startInclusive > endExclusive} or if
+ * {@code startInclusive} is negative
+ * @return the random double
+ */
+ public static double nextDouble(final double startInclusive, final double endExclusive) {
+ Validate.isTrue(endExclusive >= startInclusive,
+ "Start value must be smaller or equal to end value.");
+ Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
+
+ if (startInclusive == endExclusive) {
+ return startInclusive;
+ }
+
+ return startInclusive + ((endExclusive - startInclusive) * random().nextDouble());
+ }
+
+ /**
+ * Generates a random float within 0 - Float.MAX_VALUE.
+ *
+ * @return the random float
+ * @see #nextFloat(float, float)
+ * @since 3.5
+ */
+ public static float nextFloat() {
+ return nextFloat(0, Float.MAX_VALUE);
+ }
+
+ /**
+ * Generates a random float within the specified range.
+ *
+ * @param startInclusive
+ * the smallest value that can be returned, must be non-negative
+ * @param endExclusive
+ * the upper bound (not included)
+ * @throws IllegalArgumentException
+ * if {@code startInclusive > endExclusive} or if
+ * {@code startInclusive} is negative
+ * @return the random float
+ */
+ public static float nextFloat(final float startInclusive, final float endExclusive) {
+ Validate.isTrue(endExclusive >= startInclusive,
+ "Start value must be smaller or equal to end value.");
+ Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
+
+ if (startInclusive == endExclusive) {
+ return startInclusive;
+ }
+
+ return startInclusive + ((endExclusive - startInclusive) * random().nextFloat());
+ }
+
+ /**
+ * Generates a random int within 0 - Integer.MAX_VALUE.
+ *
+ * @return the random integer
+ * @see #nextInt(int, int)
+ * @since 3.5
+ */
+ public static int nextInt() {
+ return nextInt(0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Generates a random integer within the specified range.
+ *
+ * @param startInclusive
+ * the smallest value that can be returned, must be non-negative
+ * @param endExclusive
+ * the upper bound (not included)
+ * @throws IllegalArgumentException
+ * if {@code startInclusive > endExclusive} or if
+ * {@code startInclusive} is negative
+ * @return the random integer
+ */
+ public static int nextInt(final int startInclusive, final int endExclusive) {
+ Validate.isTrue(endExclusive >= startInclusive,
+ "Start value must be smaller or equal to end value.");
+ Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
+
+ if (startInclusive == endExclusive) {
+ return startInclusive;
+ }
+
+ return startInclusive + random().nextInt(endExclusive - startInclusive);
+ }
+
+ /**
+ * Generates a random long within 0 - Long.MAX_VALUE.
+ *
+ * @return the random long
+ * @see #nextLong(long, long)
+ * @since 3.5
+ */
+ public static long nextLong() {
+ return nextLong(Long.MAX_VALUE);
+ }
+
+ /**
+ * Generates a {@code long} value between 0 (inclusive) and the specified
+ * value (exclusive).
+ *
+ * @param n Bound on the random number to be returned. Must be positive.
+ * @return a random {@code long} value between 0 (inclusive) and {@code n}
+ * (exclusive).
+ */
+ private static long nextLong(final long n) {
+ // Extracted from o.a.c.rng.core.BaseProvider.nextLong(long)
+ long bits;
+ long val;
+ do {
+ bits = random().nextLong() >>> 1;
+ val = bits % n;
+ } while (bits - val + (n - 1) < 0);
+
+ return val;
+ }
+
+ /**
+ * Generates a random long within the specified range.
+ *
+ * @param startInclusive
+ * the smallest value that can be returned, must be non-negative
+ * @param endExclusive
+ * the upper bound (not included)
+ * @throws IllegalArgumentException
+ * if {@code startInclusive > endExclusive} or if
+ * {@code startInclusive} is negative
+ * @return the random long
+ */
+ public static long nextLong(final long startInclusive, final long endExclusive) {
+ Validate.isTrue(endExclusive >= startInclusive,
+ "Start value must be smaller or equal to end value.");
+ Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
+
+ if (startInclusive == endExclusive) {
+ return startInclusive;
+ }
+
+ return startInclusive + nextLong(endExclusive - startInclusive);
+ }
+
+ private static ThreadLocalRandom random() {
+ return ThreadLocalRandom.current();
+ }
+
+ /**
+ * {@link RandomUtils} instances should NOT be constructed in standard
+ * programming. Instead, the class should be used as
+ * {@code RandomUtils.nextBytes(5);}.
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean
+ * instance to operate.
+ * </p>
+ */
+ public RandomUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/Range.java b/src/main/java/org/apache/commons/lang3/Range.java
new file mode 100644
index 000000000..66cb5ddf4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Range.java
@@ -0,0 +1,564 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * An immutable range of objects from a minimum to maximum point inclusive.
+ *
+ * <p>The objects need to either be implementations of {@link Comparable}
+ * or you need to supply a {@link Comparator}.</p>
+ *
+ * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p>
+ *
+ * @param <T> The type of range values.
+ * @since 3.0
+ */
+public class Range<T> implements Serializable {
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private enum ComparableComparator implements Comparator {
+ INSTANCE;
+
+ /**
+ * Comparable based compare implementation.
+ *
+ * @param obj1 left-hand side of comparison
+ * @param obj2 right-hand side of comparison
+ * @return negative, 0, positive comparison value
+ */
+ @Override
+ public int compare(final Object obj1, final Object obj2) {
+ return ((Comparable) obj1).compareTo(obj2);
+ }
+ }
+
+ /**
+ * Serialization version.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>The range uses the natural ordering of the elements to determine where
+ * values lie in the range.</p>
+ *
+ * <p>The arguments may be passed in the order (min,max) or (max,min).
+ * The getMinimum and getMaximum methods will return the correct values.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param fromInclusive the first value that defines the edge of the range, inclusive
+ * @param toInclusive the second value that defines the edge of the range, inclusive
+ * @return the range object, not null
+ * @throws NullPointerException when fromInclusive is null.
+ * @throws NullPointerException when toInclusive is null.
+ * @throws ClassCastException if the elements are not {@link Comparable}
+ * @deprecated Use {@link #of(Comparable, Comparable)}.
+ */
+ @Deprecated
+ public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
+ return of(fromInclusive, toInclusive, null);
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>The range uses the specified {@link Comparator} to determine where
+ * values lie in the range.</p>
+ *
+ * <p>The arguments may be passed in the order (min,max) or (max,min).
+ * The getMinimum and getMaximum methods will return the correct values.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param fromInclusive the first value that defines the edge of the range, inclusive
+ * @param toInclusive the second value that defines the edge of the range, inclusive
+ * @param comparator the comparator to be used, null for natural ordering
+ * @return the range object, not null
+ * @throws NullPointerException when fromInclusive is null.
+ * @throws NullPointerException when toInclusive is null.
+ * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
+ * @deprecated Use {@link #of(Object, Object, Comparator)}.
+ */
+ @Deprecated
+ public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
+ return new Range<>(fromInclusive, toInclusive, comparator);
+ }
+
+ /**
+ * Creates a range using the specified element as both the minimum
+ * and maximum in this range.
+ *
+ * <p>The range uses the natural ordering of the elements to determine where
+ * values lie in the range.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param element the value to use for this range, not null
+ * @return the range object, not null
+ * @throws NullPointerException if the element is null
+ * @throws ClassCastException if the element is not {@link Comparable}
+ */
+ public static <T extends Comparable<? super T>> Range<T> is(final T element) {
+ return of(element, element, null);
+ }
+
+ /**
+ * Creates a range using the specified element as both the minimum
+ * and maximum in this range.
+ *
+ * <p>The range uses the specified {@link Comparator} to determine where
+ * values lie in the range.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param element the value to use for this range, must not be {@code null}
+ * @param comparator the comparator to be used, null for natural ordering
+ * @return the range object, not null
+ * @throws NullPointerException if the element is null
+ * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
+ */
+ public static <T> Range<T> is(final T element, final Comparator<T> comparator) {
+ return of(element, element, comparator);
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>The range uses the natural ordering of the elements to determine where
+ * values lie in the range.</p>
+ *
+ * <p>The arguments may be passed in the order (min,max) or (max,min).
+ * The getMinimum and getMaximum methods will return the correct values.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param fromInclusive the first value that defines the edge of the range, inclusive
+ * @param toInclusive the second value that defines the edge of the range, inclusive
+ * @return the range object, not null
+ * @throws NullPointerException if either element is null
+ * @throws ClassCastException if the elements are not {@link Comparable}
+ * @since 3.13.0
+ */
+ public static <T extends Comparable<? super T>> Range<T> of(final T fromInclusive, final T toInclusive) {
+ return of(fromInclusive, toInclusive, null);
+ }
+
+ /**
+ * Creates a range with the specified minimum and maximum values (both inclusive).
+ *
+ * <p>The range uses the specified {@link Comparator} to determine where
+ * values lie in the range.</p>
+ *
+ * <p>The arguments may be passed in the order (min,max) or (max,min).
+ * The getMinimum and getMaximum methods will return the correct values.</p>
+ *
+ * @param <T> the type of the elements in this range
+ * @param fromInclusive the first value that defines the edge of the range, inclusive
+ * @param toInclusive the second value that defines the edge of the range, inclusive
+ * @param comparator the comparator to be used, null for natural ordering
+ * @return the range object, not null
+ * @throws NullPointerException when fromInclusive is null.
+ * @throws NullPointerException when toInclusive is null.
+ * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
+ * @since 3.13.0
+ */
+ public static <T> Range<T> of(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
+ return new Range<>(fromInclusive, toInclusive, comparator);
+ }
+
+ /**
+ * The ordering scheme used in this range.
+ */
+ private final Comparator<T> comparator;
+
+ /**
+ * Cached output hashCode (class is immutable).
+ */
+ private transient int hashCode;
+
+ /**
+ * The maximum value in this range (inclusive).
+ */
+ private final T maximum;
+
+ /**
+ * The minimum value in this range (inclusive).
+ */
+ private final T minimum;
+
+ /**
+ * Cached output toString (class is immutable).
+ */
+ private transient String toString;
+
+ /**
+ * Creates an instance.
+ *
+ * @param element1 the first element, not null
+ * @param element2 the second element, not null
+ * @param comp the comparator to be used, null for natural ordering
+ * @throws NullPointerException when element1 is null.
+ * @throws NullPointerException when element2 is null.
+ */
+ @SuppressWarnings("unchecked")
+ Range(final T element1, final T element2, final Comparator<T> comp) {
+ Objects.requireNonNull(element1, "element1");
+ Objects.requireNonNull(element2, "element2");
+ if (comp == null) {
+ this.comparator = ComparableComparator.INSTANCE;
+ } else {
+ this.comparator = comp;
+ }
+ if (this.comparator.compare(element1, element2) < 1) {
+ this.minimum = element1;
+ this.maximum = element2;
+ } else {
+ this.minimum = element2;
+ this.maximum = element1;
+ }
+ }
+
+ /**
+ * Checks whether the specified element occurs within this range.
+ *
+ * @param element the element to check for, null returns false
+ * @return true if the specified element occurs within this range
+ */
+ public boolean contains(final T element) {
+ if (element == null) {
+ return false;
+ }
+ return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
+ }
+
+ /**
+ * Checks whether this range contains all the elements of the specified range.
+ *
+ * <p>This method may fail if the ranges have two different comparators or element types.</p>
+ *
+ * @param otherRange the range to check, null returns false
+ * @return true if this range contains the specified range
+ * @throws RuntimeException if ranges cannot be compared
+ */
+ public boolean containsRange(final Range<T> otherRange) {
+ if (otherRange == null) {
+ return false;
+ }
+ return contains(otherRange.minimum)
+ && contains(otherRange.maximum);
+ }
+
+ /**
+ * Checks where the specified element occurs relative to this range.
+ *
+ * <p>The API is reminiscent of the Comparable interface returning {@code -1} if
+ * the element is before the range, {@code 0} if contained within the range and
+ * {@code 1} if the element is after the range.</p>
+ *
+ * @param element the element to check for, not null
+ * @return -1, 0 or +1 depending on the element's location relative to the range
+ * @throws NullPointerException if {@code element} is {@code null}
+ */
+ public int elementCompareTo(final T element) {
+ // Comparable API says throw NPE on null
+ Objects.requireNonNull(element, "element");
+ if (isAfter(element)) {
+ return -1;
+ }
+ if (isBefore(element)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Compares this range to another object to test if they are equal..
+ *
+ * <p>To be equal, the minimum and maximum values must be equal, which
+ * ignores any differences in the comparator.</p>
+ *
+ * @param obj the reference object with which to compare
+ * @return true if this object is equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ @SuppressWarnings("unchecked") // OK because we checked the class above
+ final
+ Range<T> range = (Range<T>) obj;
+ return minimum.equals(range.minimum) &&
+ maximum.equals(range.maximum);
+ }
+
+ /**
+ * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if
+ * below, or the range maximum if above.
+ *
+ * <pre>
+ * Range&lt;Integer&gt; range = Range.between(16, 64);
+ * range.fit(-9) --&gt; 16
+ * range.fit(0) --&gt; 16
+ * range.fit(15) --&gt; 16
+ * range.fit(16) --&gt; 16
+ * range.fit(17) --&gt; 17
+ * ...
+ * range.fit(63) --&gt; 63
+ * range.fit(64) --&gt; 64
+ * range.fit(99) --&gt; 64
+ * </pre>
+ * @param element the element to check for, not null
+ * @return the minimum, the element, or the maximum depending on the element's location relative to the range
+ * @throws NullPointerException if {@code element} is {@code null}
+ * @since 3.10
+ */
+ public T fit(final T element) {
+ // Comparable API says throw NPE on null
+ Objects.requireNonNull(element, "element");
+ if (isAfter(element)) {
+ return minimum;
+ }
+ if (isBefore(element)) {
+ return maximum;
+ }
+ return element;
+ }
+
+ /**
+ * Gets the comparator being used to determine if objects are within the range.
+ *
+ * <p>Natural ordering uses an internal comparator implementation, thus this
+ * method never returns null. See {@link #isNaturalOrdering()}.</p>
+ *
+ * @return the comparator being used, not null
+ */
+ public Comparator<T> getComparator() {
+ return comparator;
+ }
+
+ /**
+ * Gets the maximum value in this range.
+ *
+ * @return the maximum value in this range, not null
+ */
+ public T getMaximum() {
+ return maximum;
+ }
+
+ /**
+ * Gets the minimum value in this range.
+ *
+ * @return the minimum value in this range, not null
+ */
+ public T getMinimum() {
+ return minimum;
+ }
+
+ /**
+ * Gets a suitable hash code for the range.
+ *
+ * @return a hash code value for this object
+ */
+ @Override
+ public int hashCode() {
+ int result = hashCode;
+ if (hashCode == 0) {
+ result = 17;
+ result = 37 * result + getClass().hashCode();
+ result = 37 * result + minimum.hashCode();
+ result = 37 * result + maximum.hashCode();
+ hashCode = result;
+ }
+ return result;
+ }
+
+ /**
+ * Calculate the intersection of {@code this} and an overlapping Range.
+ * @param other overlapping Range
+ * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal)
+ * @throws IllegalArgumentException if {@code other} does not overlap {@code this}
+ * @since 3.0.1
+ */
+ public Range<T> intersectionWith(final Range<T> other) {
+ if (!this.isOverlappedBy(other)) {
+ throw new IllegalArgumentException(String.format(
+ "Cannot calculate intersection with non-overlapping range %s", other));
+ }
+ if (this.equals(other)) {
+ return this;
+ }
+ final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
+ final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
+ return of(min, max, getComparator());
+ }
+
+ /**
+ * Checks whether this range is after the specified element.
+ *
+ * @param element the element to check for, null returns false
+ * @return true if this range is entirely after the specified element
+ */
+ public boolean isAfter(final T element) {
+ if (element == null) {
+ return false;
+ }
+ return comparator.compare(element, minimum) < 0;
+ }
+
+ /**
+ * Checks whether this range is completely after the specified range.
+ *
+ * <p>This method may fail if the ranges have two different comparators or element types.</p>
+ *
+ * @param otherRange the range to check, null returns false
+ * @return true if this range is completely after the specified range
+ * @throws RuntimeException if ranges cannot be compared
+ */
+ public boolean isAfterRange(final Range<T> otherRange) {
+ if (otherRange == null) {
+ return false;
+ }
+ return isAfter(otherRange.maximum);
+ }
+
+ /**
+ * Checks whether this range is before the specified element.
+ *
+ * @param element the element to check for, null returns false
+ * @return true if this range is entirely before the specified element
+ */
+ public boolean isBefore(final T element) {
+ if (element == null) {
+ return false;
+ }
+ return comparator.compare(element, maximum) > 0;
+ }
+
+ /**
+ * Checks whether this range is completely before the specified range.
+ *
+ * <p>This method may fail if the ranges have two different comparators or element types.</p>
+ *
+ * @param otherRange the range to check, null returns false
+ * @return true if this range is completely before the specified range
+ * @throws RuntimeException if ranges cannot be compared
+ */
+ public boolean isBeforeRange(final Range<T> otherRange) {
+ if (otherRange == null) {
+ return false;
+ }
+ return isBefore(otherRange.minimum);
+ }
+
+ /**
+ * Checks whether this range ends with the specified element.
+ *
+ * @param element the element to check for, null returns false
+ * @return true if the specified element occurs within this range
+ */
+ public boolean isEndedBy(final T element) {
+ if (element == null) {
+ return false;
+ }
+ return comparator.compare(element, maximum) == 0;
+ }
+
+ /**
+ * Whether or not the Range is using the natural ordering of the elements.
+ *
+ * <p>Natural ordering uses an internal comparator implementation, thus this
+ * method is the only way to check if a null comparator was specified.</p>
+ *
+ * @return true if using natural ordering
+ */
+ public boolean isNaturalOrdering() {
+ return comparator == ComparableComparator.INSTANCE;
+ }
+
+ /**
+ * Checks whether this range is overlapped by the specified range.
+ *
+ * <p>Two ranges overlap if there is at least one element in common.</p>
+ *
+ * <p>This method may fail if the ranges have two different comparators or element types.</p>
+ *
+ * @param otherRange the range to test, null returns false
+ * @return true if the specified range overlaps with this
+ * range; otherwise, {@code false}
+ * @throws RuntimeException if ranges cannot be compared
+ */
+ public boolean isOverlappedBy(final Range<T> otherRange) {
+ if (otherRange == null) {
+ return false;
+ }
+ return otherRange.contains(minimum)
+ || otherRange.contains(maximum)
+ || contains(otherRange.minimum);
+ }
+
+ /**
+ * Checks whether this range starts with the specified element.
+ *
+ * @param element the element to check for, null returns false
+ * @return true if the specified element occurs within this range
+ */
+ public boolean isStartedBy(final T element) {
+ if (element == null) {
+ return false;
+ }
+ return comparator.compare(element, minimum) == 0;
+ }
+
+ /**
+ * Gets the range as a {@link String}.
+ *
+ * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
+ *
+ * @return the {@link String} representation of this range
+ */
+ @Override
+ public String toString() {
+ if (toString == null) {
+ toString = "[" + minimum + ".." + maximum + "]";
+ }
+ return toString;
+ }
+
+ /**
+ * Formats the receiver using the given format.
+ *
+ * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
+ * be used to embed the minimum, maximum and comparator.
+ * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
+ * and {@code %3$s} for the comparator.
+ * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
+ *
+ * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null
+ * @return the formatted string, not null
+ */
+ public String toString(final String format) {
+ return String.format(format, minimum, maximum, comparator);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/RegExUtils.java b/src/main/java/org/apache/commons/lang3/RegExUtils.java
new file mode 100644
index 000000000..58fa38c4e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/RegExUtils.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.regex.Pattern;
+
+/**
+ * Helpers to process Strings using regular expressions.
+ * @see java.util.regex.Pattern
+ * @since 3.8
+ */
+public class RegExUtils {
+
+ /**
+ * Removes each substring of the text String that matches the given regular expression pattern.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code pattern.matcher(text).replaceAll(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.removeAll(null, *) = null
+ * StringUtils.removeAll("any", (Pattern) null) = "any"
+ * StringUtils.removeAll("any", Pattern.compile("")) = "any"
+ * StringUtils.removeAll("any", Pattern.compile(".*")) = ""
+ * StringUtils.removeAll("any", Pattern.compile(".+")) = ""
+ * StringUtils.removeAll("abc", Pattern.compile(".?")) = ""
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", Pattern.compile("&lt;.*&gt;")) = "A\nB"
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", Pattern.compile("(?s)&lt;.*&gt;")) = "AB"
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", Pattern.compile("&lt;.*&gt;", Pattern.DOTALL)) = "AB"
+ * StringUtils.removeAll("ABCabc123abc", Pattern.compile("[a-z]")) = "ABC123"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with any removes processed,
+ * {@code null} if null String input
+ *
+ * @see #replaceAll(String, Pattern, String)
+ * @see java.util.regex.Matcher#replaceAll(String)
+ * @see java.util.regex.Pattern
+ */
+ public static String removeAll(final String text, final Pattern regex) {
+ return replaceAll(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ * Removes each substring of the text String that matches the given regular expression.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceAll(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>Unlike in the {@link #removePattern(String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.removeAll(null, *) = null
+ * StringUtils.removeAll("any", (String) null) = "any"
+ * StringUtils.removeAll("any", "") = "any"
+ * StringUtils.removeAll("any", ".*") = ""
+ * StringUtils.removeAll("any", ".+") = ""
+ * StringUtils.removeAll("abc", ".?") = ""
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "A\nB"
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", "(?s)&lt;.*&gt;") = "AB"
+ * StringUtils.removeAll("ABCabc123abc", "[a-z]") = "ABC123"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with any removes processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceAll(String, String, String)
+ * @see #removePattern(String, String)
+ * @see String#replaceAll(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ */
+ public static String removeAll(final String text, final String regex) {
+ return replaceAll(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ * Removes the first substring of the text string that matches the given regular expression pattern.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code pattern.matcher(text).replaceFirst(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.removeFirst(null, *) = null
+ * StringUtils.removeFirst("any", (Pattern) null) = "any"
+ * StringUtils.removeFirst("any", Pattern.compile("")) = "any"
+ * StringUtils.removeFirst("any", Pattern.compile(".*")) = ""
+ * StringUtils.removeFirst("any", Pattern.compile(".+")) = ""
+ * StringUtils.removeFirst("abc", Pattern.compile(".?")) = "bc"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", Pattern.compile("&lt;.*&gt;")) = "A\n&lt;__&gt;B"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", Pattern.compile("(?s)&lt;.*&gt;")) = "AB"
+ * StringUtils.removeFirst("ABCabc123", Pattern.compile("[a-z]")) = "ABCbc123"
+ * StringUtils.removeFirst("ABCabc123abc", Pattern.compile("[a-z]+")) = "ABC123abc"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression pattern to which this string is to be matched
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @see #replaceFirst(String, Pattern, String)
+ * @see java.util.regex.Matcher#replaceFirst(String)
+ * @see java.util.regex.Pattern
+ */
+ public static String removeFirst(final String text, final Pattern regex) {
+ return replaceFirst(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ * Removes the first substring of the text string that matches the given regular expression.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceFirst(regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceFirst(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.removeFirst(null, *) = null
+ * StringUtils.removeFirst("any", (String) null) = "any"
+ * StringUtils.removeFirst("any", "") = "any"
+ * StringUtils.removeFirst("any", ".*") = ""
+ * StringUtils.removeFirst("any", ".+") = ""
+ * StringUtils.removeFirst("abc", ".?") = "bc"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "A\n&lt;__&gt;B"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", "(?s)&lt;.*&gt;") = "AB"
+ * StringUtils.removeFirst("ABCabc123", "[a-z]") = "ABCbc123"
+ * StringUtils.removeFirst("ABCabc123abc", "[a-z]+") = "ABC123abc"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceFirst(String, String, String)
+ * @see String#replaceFirst(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ */
+ public static String removeFirst(final String text, final String regex) {
+ return replaceFirst(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ * Removes each substring of the source String that matches the given regular expression using the DOTALL option.
+ *
+ * This call is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(&quot;(?s)&quot; + regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex, Pattern.DOTALL).matcher(text).replaceAll(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.removePattern(null, *) = null
+ * StringUtils.removePattern("any", (String) null) = "any"
+ * StringUtils.removePattern("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "AB"
+ * StringUtils.removePattern("ABCabc123", "[a-z]") = "ABC123"
+ * </pre>
+ *
+ * @param text
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @return The resulting {@link String}
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ */
+ public static String removePattern(final String text, final String regex) {
+ return replacePattern(text, regex, StringUtils.EMPTY);
+ }
+
+ /**
+ * Replaces each substring of the text String that matches the given regular expression pattern with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code pattern.matcher(text).replaceAll(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceAll(null, *, *) = null
+ * StringUtils.replaceAll("any", (Pattern) null, *) = "any"
+ * StringUtils.replaceAll("any", *, null) = "any"
+ * StringUtils.replaceAll("", Pattern.compile(""), "zzz") = "zzz"
+ * StringUtils.replaceAll("", Pattern.compile(".*"), "zzz") = "zzz"
+ * StringUtils.replaceAll("", Pattern.compile(".+"), "zzz") = ""
+ * StringUtils.replaceAll("abc", Pattern.compile(""), "ZZ") = "ZZaZZbZZcZZ"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", Pattern.compile("&lt;.*&gt;"), "z") = "z\nz"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", Pattern.compile("&lt;.*&gt;", Pattern.DOTALL), "z") = "z"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", Pattern.compile("(?s)&lt;.*&gt;"), "z") = "z"
+ * StringUtils.replaceAll("ABCabc123", Pattern.compile("[a-z]"), "_") = "ABC___123"
+ * StringUtils.replaceAll("ABCabc123", Pattern.compile("[^A-Z0-9]+"), "_") = "ABC_123"
+ * StringUtils.replaceAll("ABCabc123", Pattern.compile("[^A-Z0-9]+"), "") = "ABC123"
+ * StringUtils.replaceAll("Lorem ipsum dolor sit", Pattern.compile("( +)([a-z]+)"), "_$2") = "Lorem_ipsum_dolor_sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression pattern to which this string is to be matched
+ * @param replacement the string to be substituted for each match
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ *
+ * @see java.util.regex.Matcher#replaceAll(String)
+ * @see java.util.regex.Pattern
+ */
+ public static String replaceAll(final String text, final Pattern regex, final String replacement) {
+ if (ObjectUtils.anyNull(text, regex, replacement)) {
+ return text;
+ }
+ return regex.matcher(text).replaceAll(replacement);
+ }
+
+ /**
+ * Replaces each substring of the text String that matches the given regular expression
+ * with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceAll(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>Unlike in the {@link #replacePattern(String, String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.replaceAll(null, *, *) = null
+ * StringUtils.replaceAll("any", (String) null, *) = "any"
+ * StringUtils.replaceAll("any", *, null) = "any"
+ * StringUtils.replaceAll("", "", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".+", "zzz") = ""
+ * StringUtils.replaceAll("abc", "", "ZZ") = "ZZaZZbZZcZZ"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z\nz"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", "(?s)&lt;.*&gt;", "z") = "z"
+ * StringUtils.replaceAll("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replaceAll("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for each match
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ */
+ public static String replaceAll(final String text, final String regex, final String replacement) {
+ if (ObjectUtils.anyNull(text, regex, replacement)) {
+ return text;
+ }
+ return text.replaceAll(regex, replacement);
+ }
+
+ /**
+ * Replaces the first substring of the text string that matches the given regular expression pattern
+ * with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code pattern.matcher(text).replaceFirst(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceFirst(null, *, *) = null
+ * StringUtils.replaceFirst("any", (Pattern) null, *) = "any"
+ * StringUtils.replaceFirst("any", *, null) = "any"
+ * StringUtils.replaceFirst("", Pattern.compile(""), "zzz") = "zzz"
+ * StringUtils.replaceFirst("", Pattern.compile(".*"), "zzz") = "zzz"
+ * StringUtils.replaceFirst("", Pattern.compile(".+"), "zzz") = ""
+ * StringUtils.replaceFirst("abc", Pattern.compile(""), "ZZ") = "ZZabc"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", Pattern.compile("&lt;.*&gt;"), "z") = "z\n&lt;__&gt;"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", Pattern.compile("(?s)&lt;.*&gt;"), "z") = "z"
+ * StringUtils.replaceFirst("ABCabc123", Pattern.compile("[a-z]"), "_") = "ABC_bc123"
+ * StringUtils.replaceFirst("ABCabc123abc", Pattern.compile("[^A-Z0-9]+"), "_") = "ABC_123abc"
+ * StringUtils.replaceFirst("ABCabc123abc", Pattern.compile("[^A-Z0-9]+"), "") = "ABC123abc"
+ * StringUtils.replaceFirst("Lorem ipsum dolor sit", Pattern.compile("( +)([a-z]+)"), "_$2") = "Lorem_ipsum dolor sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression pattern to which this string is to be matched
+ * @param replacement the string to be substituted for the first match
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @see java.util.regex.Matcher#replaceFirst(String)
+ * @see java.util.regex.Pattern
+ */
+ public static String replaceFirst(final String text, final Pattern regex, final String replacement) {
+ if (text == null || regex == null || replacement == null) {
+ return text;
+ }
+ return regex.matcher(text).replaceFirst(replacement);
+ }
+
+ /**
+ * Replaces the first substring of the text string that matches the given regular expression
+ * with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceFirst(regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceFirst(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.replaceFirst(null, *, *) = null
+ * StringUtils.replaceFirst("any", (String) null, *) = "any"
+ * StringUtils.replaceFirst("any", *, null) = "any"
+ * StringUtils.replaceFirst("", "", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".+", "zzz") = ""
+ * StringUtils.replaceFirst("abc", "", "ZZ") = "ZZabc"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z\n&lt;__&gt;"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", "(?s)&lt;.*&gt;", "z") = "z"
+ * StringUtils.replaceFirst("ABCabc123", "[a-z]", "_") = "ABC_bc123"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "_") = "ABC_123abc"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "") = "ABC123abc"
+ * StringUtils.replaceFirst("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum dolor sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for the first match
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see String#replaceFirst(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ */
+ public static String replaceFirst(final String text, final String regex, final String replacement) {
+ if (text == null || regex == null || replacement == null) {
+ return text;
+ }
+ return text.replaceFirst(regex, replacement);
+ }
+
+ /**
+ * Replaces each substring of the source String that matches the given regular expression with the given
+ * replacement using the {@link Pattern#DOTALL} option. DOTALL is also known as single-line mode in Perl.
+ *
+ * This call is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(&quot;(?s)&quot; + regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex, Pattern.DOTALL).matcher(text).replaceAll(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replacePattern(null, *, *) = null
+ * StringUtils.replacePattern("any", (String) null, *) = "any"
+ * StringUtils.replacePattern("any", *, null) = "any"
+ * StringUtils.replacePattern("", "", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".*", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".+", "zzz") = ""
+ * StringUtils.replacePattern("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z"
+ * StringUtils.replacePattern("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replacePattern("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ * </pre>
+ *
+ * @param text
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @param replacement
+ * the string to be substituted for each match
+ * @return The resulting {@link String}
+ * @see #replaceAll(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ */
+ public static String replacePattern(final String text, final String regex, final String replacement) {
+ if (ObjectUtils.anyNull(text, regex, replacement)) {
+ return text;
+ }
+ return Pattern.compile(regex, Pattern.DOTALL).matcher(text).replaceAll(replacement);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/SerializationException.java b/src/main/java/org/apache/commons/lang3/SerializationException.java
new file mode 100644
index 000000000..c68787312
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/SerializationException.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+/**
+ * Exception thrown when the Serialization process fails.
+ *
+ * <p>The original error is wrapped within this one.</p>
+ *
+ * <p>#NotThreadSafe# because Throwable is not thread-safe</p>
+ * @since 1.0
+ */
+public class SerializationException extends RuntimeException {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 4029025366392702726L;
+
+ /**
+ * Constructs a new {@link SerializationException} without specified
+ * detail message.
+ */
+ public SerializationException() {
+ }
+
+ /**
+ * Constructs a new {@link SerializationException} with specified
+ * detail message.
+ *
+ * @param msg The error message.
+ */
+ public SerializationException(final String msg) {
+ 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}.
+ *
+ * @param msg The error message.
+ * @param cause The {@link Exception} or {@link Error}
+ * that caused this exception to be thrown.
+ */
+ public SerializationException(final String msg, final Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/SerializationUtils.java b/src/main/java/org/apache/commons/lang3/SerializationUtils.java
new file mode 100644
index 000000000..b608b7dca
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/SerializationUtils.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Assists with the serialization process and performs additional functionality based
+ * on serialization.
+ *
+ * <ul>
+ * <li>Deep clone using serialization
+ * <li>Serialize managing finally and IOException
+ * <li>Deserialize managing finally and IOException
+ * </ul>
+ *
+ * <p>This class throws exceptions for invalid {@code null} inputs.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 1.0
+ */
+public class SerializationUtils {
+
+ /**
+ * Custom specialization of the standard JDK {@link java.io.ObjectInputStream}
+ * that uses a custom {@link ClassLoader} to resolve a class.
+ * If the specified {@link ClassLoader} is not able to resolve the class,
+ * the context classloader of the current thread will be used.
+ * This way, the standard deserialization work also in web-application
+ * containers and application servers, no matter in which of the
+ * {@link ClassLoader} the particular class that encapsulates
+ * serialization/deserialization lives.
+ *
+ * <p>For more in-depth information about the problem for which this
+ * class here is a workaround, see the JIRA issue LANG-626.</p>
+ */
+ static class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
+ private static final Map<String, Class<?>> primitiveTypes =
+ new HashMap<>();
+
+ static {
+ primitiveTypes.put("byte", byte.class);
+ primitiveTypes.put("short", short.class);
+ primitiveTypes.put("int", int.class);
+ primitiveTypes.put("long", long.class);
+ primitiveTypes.put("float", float.class);
+ primitiveTypes.put("double", double.class);
+ primitiveTypes.put("boolean", boolean.class);
+ primitiveTypes.put("char", char.class);
+ primitiveTypes.put("void", void.class);
+ }
+
+ private final ClassLoader classLoader;
+
+ /**
+ * Constructor.
+ * @param in The {@link InputStream}.
+ * @param classLoader classloader to use
+ * @throws IOException if an I/O error occurs while reading stream header.
+ * @see java.io.ObjectInputStream
+ */
+ ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
+ super(in);
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Overridden version that uses the parameterized {@link ClassLoader} or the {@link ClassLoader}
+ * of the current {@link Thread} to resolve the class.
+ * @param desc An instance of class {@link ObjectStreamClass}.
+ * @return A {@link Class} object corresponding to {@code desc}.
+ * @throws IOException Any of the usual Input/Output exceptions.
+ * @throws ClassNotFoundException If class of a serialized object cannot be found.
+ */
+ @Override
+ protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+ final String name = desc.getName();
+ try {
+ return Class.forName(name, false, classLoader);
+ } catch (final ClassNotFoundException ex) {
+ try {
+ return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
+ } catch (final ClassNotFoundException cnfe) {
+ final Class<?> cls = primitiveTypes.get(name);
+ if (cls != null) {
+ return cls;
+ }
+ throw cnfe;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Deep clone an {@link Object} using serialization.
+ *
+ * <p>This is many times slower than writing clone methods by hand
+ * on all objects in your object graph. However, for complex object
+ * graphs, or for those that don't support deep cloning this can
+ * be a simple alternative implementation. Of course all the objects
+ * must be {@link Serializable}.</p>
+ *
+ * @param <T> the type of the object involved
+ * @param object the {@link Serializable} object to clone
+ * @return the cloned object
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ public static <T extends Serializable> T clone(final T object) {
+ if (object == null) {
+ return null;
+ }
+ final byte[] objectData = serialize(object);
+ final ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
+
+ final Class<T> cls = ObjectUtils.getClass(object);
+ try (ClassLoaderAwareObjectInputStream in = new ClassLoaderAwareObjectInputStream(bais, cls.getClassLoader())) {
+ /*
+ * when we serialize and deserialize an object, it is reasonable to assume the deserialized object is of the
+ * same type as the original serialized object
+ */
+ return cls.cast(in.readObject());
+
+ } catch (final ClassNotFoundException | IOException ex) {
+ throw new SerializationException(
+ String.format("%s while reading cloned object data", ex.getClass().getSimpleName()), ex);
+ }
+ }
+
+ /**
+ * Deserializes a single {@link Object} from an array of bytes.
+ *
+ * <p>
+ * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
+ * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
+ * Note that in both cases, the ClassCastException is in the call site, not in this method.
+ * </p>
+ *
+ * @param <T> the object type to be deserialized
+ * @param objectData
+ * the serialized object, must not be null
+ * @return the deserialized object
+ * @throws NullPointerException if {@code objectData} is {@code null}
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ public static <T> T deserialize(final byte[] objectData) {
+ Objects.requireNonNull(objectData, "objectData");
+ return deserialize(new ByteArrayInputStream(objectData));
+ }
+
+ /**
+ * Deserializes an {@link Object} from the specified stream.
+ *
+ * <p>
+ * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also
+ * exception handling, in the application code.
+ * </p>
+ *
+ * <p>
+ * The stream passed in is not buffered internally within this method. This is the responsibility of your
+ * application if desired.
+ * </p>
+ *
+ * <p>
+ * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
+ * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
+ * Note that in both cases, the ClassCastException is in the call site, not in this method.
+ * </p>
+ *
+ * @param <T> the object type to be deserialized
+ * @param inputStream
+ * the serialized object input stream, must not be null
+ * @return the deserialized object
+ * @throws NullPointerException if {@code inputStream} is {@code null}
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ @SuppressWarnings("resource") // inputStream is managed by the caller
+ public static <T> T deserialize(final InputStream inputStream) {
+ Objects.requireNonNull(inputStream, "inputStream");
+ try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
+ @SuppressWarnings("unchecked")
+ final T obj = (T) in.readObject();
+ return obj;
+ } catch (final ClassNotFoundException | IOException ex) {
+ throw new SerializationException(ex);
+ }
+ }
+
+ /**
+ * Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that
+ * implement {@link Serializable}.
+ *
+ * @param <T>
+ * the type of the object involved
+ * @param obj
+ * the object to roundtrip
+ * @return the serialized and deserialized object
+ * @since 3.3
+ */
+ @SuppressWarnings("unchecked") // OK, because we serialized a type `T`
+ public static <T extends Serializable> T roundtrip(final T obj) {
+ return (T) deserialize(serialize(obj));
+ }
+
+ /**
+ * Serializes an {@link Object} to a byte array for
+ * storage/serialization.
+ *
+ * @param obj the object to serialize to bytes
+ * @return a byte[] with the converted Serializable
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ public static byte[] serialize(final Serializable obj) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
+ serialize(obj, baos);
+ return baos.toByteArray();
+ }
+
+ /**
+ * Serializes an {@link Object} to the specified stream.
+ *
+ * <p>The stream will be closed once the object is written.
+ * This avoids the need for a finally clause, and maybe also exception
+ * handling, in the application code.</p>
+ *
+ * <p>The stream passed in is not buffered internally within this method.
+ * This is the responsibility of your application if desired.</p>
+ *
+ * @param obj the object to serialize to bytes, may be null
+ * @param outputStream the stream to write to, must not be null
+ * @throws NullPointerException if {@code outputStream} is {@code null}
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ @SuppressWarnings("resource") // outputStream is managed by the caller
+ public static void serialize(final Serializable obj, final OutputStream outputStream) {
+ Objects.requireNonNull(outputStream, "outputStream");
+ try (ObjectOutputStream out = new ObjectOutputStream(outputStream)) {
+ out.writeObject(obj);
+ } catch (final IOException ex) {
+ throw new SerializationException(ex);
+ }
+ }
+
+ /**
+ * SerializationUtils instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code SerializationUtils.clone(object)}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ * @since 2.0
+ */
+ public SerializationUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/Streams.java b/src/main/java/org/apache/commons/lang3/Streams.java
new file mode 100644
index 000000000..2a9201d75
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Streams.java
@@ -0,0 +1,551 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.Functions.FailableConsumer;
+import org.apache.commons.lang3.Functions.FailableFunction;
+import org.apache.commons.lang3.Functions.FailablePredicate;
+
+/**
+ * Provides utility functions, and classes for working with the
+ * {@code java.util.stream} package, or more generally, with Java 8 lambdas. More
+ * specifically, it attempts to address the fact that lambdas are supposed
+ * not to throw Exceptions, at least not checked Exceptions, AKA instances
+ * of {@link Exception}. This enforces the use of constructs like
+ * <pre>
+ * Consumer&lt;java.lang.reflect.Method&gt; consumer = m -&gt; {
+ * try {
+ * m.invoke(o, args);
+ * } catch (Throwable t) {
+ * throw Functions.rethrow(t);
+ * }
+ * };
+ * stream.forEach(consumer);
+ * </pre>
+ * Using a {@link FailableStream}, this can be rewritten as follows:
+ * <pre>
+ * Streams.failable(stream).forEach((m) -&gt; m.invoke(o, args));
+ * </pre>
+ * Obviously, the second version is much more concise and the spirit of
+ * Lambda expressions is met better than in the first version.
+ *
+ * @see Stream
+ * @see Functions
+ * @since 3.10
+ * @deprecated Use {@link org.apache.commons.lang3.stream.Streams}.
+ */
+@Deprecated
+public class Streams {
+
+ /**
+ * A reduced, and simplified version of a {@link Stream} with
+ * failable method signatures.
+ * @param <O> The streams element type.
+ * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.FailableStream}.
+ */
+ @Deprecated
+ public static class FailableStream<O> {
+
+ private Stream<O> stream;
+ private boolean terminated;
+
+ /**
+ * Constructs a new instance with the given {@code stream}.
+ * @param stream The stream.
+ */
+ public FailableStream(final Stream<O> stream) {
+ this.stream = stream;
+ }
+
+ /**
+ * Throws IllegalStateException if this stream is already terminated.
+ *
+ * @throws IllegalStateException if this stream is already terminated.
+ */
+ protected void assertNotTerminated() {
+ if (terminated) {
+ throw new IllegalStateException("This stream is already terminated.");
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * @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<O> filter(final FailablePredicate<O, ?> predicate){
+ assertNotTerminated();
+ stream = stream.filter(Functions.asPredicate(predicate));
+ return this;
+ }
+
+ /**
+ * Performs an action for each element of this stream.
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * <p>
+ * The behavior of this operation is explicitly nondeterministic.
+ * For parallel stream pipelines, this operation does <em>not</em>
+ * 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.
+ * </p>
+ *
+ * @param action a non-interfering action to perform on the elements
+ */
+ public void forEach(final FailableConsumer<O, ?> 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}
+ * encapsulates the functions used as arguments to
+ * {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for reuse of
+ * collection strategies and composition of collect operations such as
+ * multiple-level grouping or partitioning.
+ *
+ * <p>
+ * If the underlying stream is parallel, and the {@link Collector}
+ * is concurrent, and either the stream is unordered or the collector is
+ * unordered, then a concurrent reduction will be performed
+ * (see {@link Collector} for details on concurrent reduction.)
+ * </p>
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * <p>
+ * When executed in parallel, multiple intermediate results may be
+ * instantiated, populated, and merged so as to maintain isolation of
+ * mutable data structures. Therefore, even when executed in parallel
+ * with non-thread-safe data structures (such as {@link ArrayList}), no
+ * additional synchronization is needed for a parallel reduction.
+ * </p>
+ * <p>
+ * Note
+ * The following will accumulate strings into an ArrayList:
+ * </p>
+ * <pre>{@code
+ * List<String> asList = stringStream.collect(Collectors.toList());
+ * }</pre>
+ *
+ * <p>
+ * The following will classify {@code Person} objects by city:
+ * </p>
+ * <pre>{@code
+ * Map<String, List<Person>> peopleByCity
+ * = personStream.collect(Collectors.groupingBy(Person::getCity));
+ * }</pre>
+ *
+ * <p>
+ * The following will classify {@code Person} objects by state and city,
+ * cascading two {@link Collector}s together:
+ * </p>
+ * <pre>{@code
+ * Map<String, Map<String, List<Person>>> peopleByStateAndCity
+ * = personStream.collect(Collectors.groupingBy(Person::getState,
+ * Collectors.groupingBy(Person::getCity)));
+ * }</pre>
+ *
+ * @param <R> the type of the result
+ * @param <A> the intermediate accumulation type of the {@link Collector}
+ * @param collector the {@link Collector} describing the reduction
+ * @return the result of the reduction
+ * @see #collect(Supplier, BiConsumer, BiConsumer)
+ * @see Collectors
+ */
+ public <A, R> R collect(final Collector<? super O, A, R> collector) {
+ makeTerminated();
+ return stream().collect(collector);
+ }
+
+ /**
+ * Performs a mutable reduction operation on the elements of this FailableStream.
+ * A mutable reduction is one in which the reduced value is a mutable result
+ * container, such as an {@link ArrayList}, and elements are incorporated by updating
+ * the state of the result rather than by replacing the result. This produces a result equivalent to:
+ * <pre>{@code
+ * R result = supplier.get();
+ * for (T element : this stream)
+ * accumulator.accept(result, element);
+ * return result;
+ * }</pre>
+ *
+ * <p>
+ * Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations
+ * can be parallelized without requiring additional synchronization.
+ * </p>
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * <p>
+ * Note There are many existing classes in the JDK whose signatures are
+ * well-suited for use with method references as arguments to {@code collect()}.
+ * For example, the following will accumulate strings into an {@link ArrayList}:
+ * </p>
+ * <pre>{@code
+ * List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,
+ * ArrayList::addAll);
+ * }</pre>
+ *
+ * <p>
+ * The following will take a stream of strings and concatenates them into a
+ * single string:
+ * </p>
+ * <pre>{@code
+ * String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
+ * StringBuilder::append)
+ * .toString();
+ * }</pre>
+ *
+ * @param <R> type of the result
+ * @param <A> Type of the accumulator.
+ * @param supplier a function that creates a new result container. For a
+ * parallel execution, this function may be called
+ * multiple times and must return a fresh value each time.
+ * @param accumulator An associative, non-interfering, stateless function for
+ * incorporating an additional element into a result
+ * @param combiner An associative, non-interfering, stateless
+ * function for combining two values, which must be compatible with the
+ * accumulator function
+ * @return The result of the reduction
+ */
+ public <A, R> R collect(final Supplier<R> supplier, final BiConsumer<R, ? super O> accumulator, final BiConsumer<R, R> combiner) {
+ makeTerminated();
+ return stream().collect(supplier, accumulator, combiner);
+ }
+
+ /**
+ * Performs a reduction on the elements of this stream, using the provided
+ * identity value and an associative accumulation function, and returns
+ * the reduced value. This is equivalent to:
+ * <pre>{@code
+ * T result = identity;
+ * for (T element : this stream)
+ * result = accumulator.apply(result, element)
+ * return result;
+ * }</pre>
+ *
+ * but is not constrained to execute sequentially.
+ *
+ * <p>
+ * The {@code identity} value must be an identity for the accumulator
+ * function. This means that for all {@code t},
+ * {@code accumulator.apply(identity, t)} is equal to {@code t}.
+ * The {@code accumulator} function must be an associative function.
+ * </p>
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * Note Sum, min, max, average, and string concatenation are all special
+ * cases of reduction. Summing a stream of numbers can be expressed as:
+ *
+ * <pre>{@code
+ * Integer sum = integers.reduce(0, (a, b) -> a+b);
+ * }</pre>
+ *
+ * or:
+ *
+ * <pre>{@code
+ * Integer sum = integers.reduce(0, Integer::sum);
+ * }</pre>
+ *
+ * <p>
+ * While this may seem a more roundabout way to perform an aggregation
+ * compared to simply mutating a running total in a loop, reduction
+ * operations parallelize more gracefully, without needing additional
+ * synchronization and with greatly reduced risk of data races.
+ * </p>
+ *
+ * @param identity the identity value for the accumulating function
+ * @param accumulator an associative, non-interfering, stateless
+ * function for combining two values
+ * @return the result of the reduction
+ */
+ public O reduce(final O identity, final BinaryOperator<O> accumulator) {
+ makeTerminated();
+ return stream().reduce(identity, accumulator);
+ }
+
+ /**
+ * Returns a stream consisting of the results of applying the given
+ * function to the elements of this stream.
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * @param <R> The element type of the new stream
+ * @param mapper A non-interfering, stateless function to apply to each element
+ * @return the new stream
+ */
+ public <R> FailableStream<R> map(final FailableFunction<O, R, ?> 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.
+ */
+ public Stream<O> 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.
+ *
+ * <p>
+ * This is a short-circuiting terminal operation.
+ * </p>
+ *
+ * <p>
+ * Note
+ * This method evaluates the <em>universal quantification</em> 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 <em>vacuously
+ * satisfied</em> and is always {@code true} (regardless of P(x)).
+ * </p>
+ *
+ * @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<O, ?> 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.
+ *
+ * <p>
+ * This is a short-circuiting terminal operation.
+ * </p>
+ *
+ * Note
+ * This method evaluates the <em>existential quantification</em> 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<O, ?> 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:
+ * <pre>
+ * final List&lt;O&gt; list;
+ * final Method m;
+ * final Function&lt;O,String&gt; mapper = (o) -&gt; {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Functions.rethrow(t);
+ * }
+ * };
+ * final List&lt;String&gt; strList = list.stream()
+ * .map(mapper).collect(Collectors.toList());
+ * </pre>
+ * as follows:
+ * <pre>
+ * final List&lt;O&gt; list;
+ * final Method m;
+ * final List&lt;String&gt; strList = Functions.stream(list.stream())
+ * .map((o) -&gt; (String) m.invoke(o)).collect(Collectors.toList());
+ * </pre>
+ * While the second version may not be <em>quite</em> 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 <O> 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 <O> FailableStream<O> stream(final Stream<O> stream) {
+ return new FailableStream<>(stream);
+ }
+
+ /**
+ * Converts the given {@link Collection} 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:
+ * <pre>
+ * final List&lt;O&gt; list;
+ * final Method m;
+ * final Function&lt;O,String&gt; mapper = (o) -&gt; {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Functions.rethrow(t);
+ * }
+ * };
+ * final List&lt;String&gt; strList = list.stream()
+ * .map(mapper).collect(Collectors.toList());
+ * </pre>
+ * as follows:
+ * <pre>
+ * final List&lt;O&gt; list;
+ * final Method m;
+ * final List&lt;String&gt; strList = Functions.stream(list.stream())
+ * .map((o) -&gt; (String) m.invoke(o)).collect(Collectors.toList());
+ * </pre>
+ * While the second version may not be <em>quite</em> 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 <O> 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 <O> FailableStream<O> stream(final Collection<O> stream) {
+ return stream(stream.stream());
+ }
+
+ /**
+ * A Collector type for arrays.
+ *
+ * @param <O> The array type.
+ * @deprecated Use {@link org.apache.commons.lang3.stream.Streams.ArrayCollector}.
+ */
+ @Deprecated
+ public static class ArrayCollector<O> implements Collector<O, List<O>, O[]> {
+ private static final Set<Characteristics> characteristics = Collections.emptySet();
+ private final Class<O> elementType;
+
+ /**
+ * Constructs a new instance for the given element type.
+ *
+ * @param elementType The element type.
+ */
+ public ArrayCollector(final Class<O> elementType) {
+ this.elementType = elementType;
+ }
+
+ @Override
+ public Supplier<List<O>> supplier() {
+ return ArrayList::new;
+ }
+
+ @Override
+ public BiConsumer<List<O>, O> accumulator() {
+ return List::add;
+ }
+
+ @Override
+ public BinaryOperator<List<O>> combiner() {
+ return (left, right) -> {
+ left.addAll(right);
+ return left;
+ };
+ }
+
+ @Override
+ public Function<List<O>, O[]> finisher() {
+ return list -> list.toArray(ArrayUtils.newInstance(elementType, list.size()));
+ }
+
+ @Override
+ public Set<Characteristics> characteristics() {
+ return characteristics;
+ }
+ }
+
+ /**
+ * Returns a {@link Collector} that accumulates the input elements into a
+ * new array.
+ *
+ * @param pElementType Type of an element in the array.
+ * @param <O> the type of the input elements
+ * @return a {@link Collector} which collects all the input elements into an
+ * array, in encounter order
+ */
+ public static <O> Collector<O, ?, O[]> toArray(final Class<O> pElementType) {
+ return new ArrayCollector<>(pElementType);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
new file mode 100644
index 000000000..2aee2ca52
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
@@ -0,0 +1,797 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.commons.lang3.text.translate.AggregateTranslator;
+import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
+import org.apache.commons.lang3.text.translate.EntityArrays;
+import org.apache.commons.lang3.text.translate.JavaUnicodeEscaper;
+import org.apache.commons.lang3.text.translate.LookupTranslator;
+import org.apache.commons.lang3.text.translate.NumericEntityEscaper;
+import org.apache.commons.lang3.text.translate.NumericEntityUnescaper;
+import org.apache.commons.lang3.text.translate.OctalUnescaper;
+import org.apache.commons.lang3.text.translate.UnicodeUnescaper;
+import org.apache.commons.lang3.text.translate.UnicodeUnpairedSurrogateRemover;
+
+/**
+ * Escapes and unescapes {@link String}s for
+ * Java, Java Script, HTML and XML.
+ *
+ * <p>#ThreadSafe#</p>
+ * @since 2.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringEscapeUtils.html">
+ * StringEscapeUtils</a> instead
+ */
+@Deprecated
+public class StringEscapeUtils {
+
+ /* ESCAPE TRANSLATORS */
+
+ /**
+ * Translator object for escaping Java.
+ *
+ * While {@link #escapeJava(String)} is the expected method of use, this
+ * object allows the Java escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator ESCAPE_JAVA =
+ new LookupTranslator(
+ new String[][] {
+ {"\"", "\\\""},
+ {"\\", "\\\\"},
+ }).with(
+ new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE())
+ ).with(
+ JavaUnicodeEscaper.outsideOf(32, 0x7f)
+ );
+
+ /**
+ * Translator object for escaping EcmaScript/JavaScript.
+ *
+ * While {@link #escapeEcmaScript(String)} is the expected method of use, this
+ * object allows the EcmaScript escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator ESCAPE_ECMASCRIPT =
+ new AggregateTranslator(
+ new LookupTranslator(
+ new String[][] {
+ {"'", "\\'"},
+ {"\"", "\\\""},
+ {"\\", "\\\\"},
+ {"/", "\\/"}
+ }),
+ new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()),
+ JavaUnicodeEscaper.outsideOf(32, 0x7f)
+ );
+
+ /**
+ * Translator object for escaping Json.
+ *
+ * While {@link #escapeJson(String)} is the expected method of use, this
+ * object allows the Json escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.2
+ */
+ public static final CharSequenceTranslator ESCAPE_JSON =
+ new AggregateTranslator(
+ new LookupTranslator(
+ new String[][] {
+ {"\"", "\\\""},
+ {"\\", "\\\\"},
+ {"/", "\\/"}
+ }),
+ new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()),
+ JavaUnicodeEscaper.outsideOf(32, 0x7f)
+ );
+
+ /**
+ * Translator object for escaping XML.
+ *
+ * While {@link #escapeXml(String)} is the expected method of use, this
+ * object allows the XML escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ * @deprecated use {@link #ESCAPE_XML10} or {@link #ESCAPE_XML11} instead.
+ */
+ @Deprecated
+ public static final CharSequenceTranslator ESCAPE_XML =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.APOS_ESCAPE())
+ );
+
+ /**
+ * Translator object for escaping XML 1.0.
+ *
+ * While {@link #escapeXml10(String)} is the expected method of use, this
+ * object allows the XML escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.3
+ */
+ public static final CharSequenceTranslator ESCAPE_XML10 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.APOS_ESCAPE()),
+ new LookupTranslator(
+ new String[][] {
+ { "\u0000", StringUtils.EMPTY },
+ { "\u0001", StringUtils.EMPTY },
+ { "\u0002", StringUtils.EMPTY },
+ { "\u0003", StringUtils.EMPTY },
+ { "\u0004", StringUtils.EMPTY },
+ { "\u0005", StringUtils.EMPTY },
+ { "\u0006", StringUtils.EMPTY },
+ { "\u0007", StringUtils.EMPTY },
+ { "\u0008", StringUtils.EMPTY },
+ { "\u000b", StringUtils.EMPTY },
+ { "\u000c", StringUtils.EMPTY },
+ { "\u000e", StringUtils.EMPTY },
+ { "\u000f", StringUtils.EMPTY },
+ { "\u0010", StringUtils.EMPTY },
+ { "\u0011", StringUtils.EMPTY },
+ { "\u0012", StringUtils.EMPTY },
+ { "\u0013", StringUtils.EMPTY },
+ { "\u0014", StringUtils.EMPTY },
+ { "\u0015", StringUtils.EMPTY },
+ { "\u0016", StringUtils.EMPTY },
+ { "\u0017", StringUtils.EMPTY },
+ { "\u0018", StringUtils.EMPTY },
+ { "\u0019", StringUtils.EMPTY },
+ { "\u001a", StringUtils.EMPTY },
+ { "\u001b", StringUtils.EMPTY },
+ { "\u001c", StringUtils.EMPTY },
+ { "\u001d", StringUtils.EMPTY },
+ { "\u001e", StringUtils.EMPTY },
+ { "\u001f", StringUtils.EMPTY },
+ { "\ufffe", StringUtils.EMPTY },
+ { "\uffff", StringUtils.EMPTY }
+ }),
+ NumericEntityEscaper.between(0x7f, 0x84),
+ NumericEntityEscaper.between(0x86, 0x9f),
+ new UnicodeUnpairedSurrogateRemover()
+ );
+
+ /**
+ * Translator object for escaping XML 1.1.
+ *
+ * While {@link #escapeXml11(String)} is the expected method of use, this
+ * object allows the XML escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.3
+ */
+ public static final CharSequenceTranslator ESCAPE_XML11 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.APOS_ESCAPE()),
+ new LookupTranslator(
+ new String[][] {
+ { "\u0000", StringUtils.EMPTY },
+ { "\u000b", "&#11;" },
+ { "\u000c", "&#12;" },
+ { "\ufffe", StringUtils.EMPTY },
+ { "\uffff", StringUtils.EMPTY }
+ }),
+ NumericEntityEscaper.between(0x1, 0x8),
+ NumericEntityEscaper.between(0xe, 0x1f),
+ NumericEntityEscaper.between(0x7f, 0x84),
+ NumericEntityEscaper.between(0x86, 0x9f),
+ new UnicodeUnpairedSurrogateRemover()
+ );
+
+ /**
+ * Translator object for escaping HTML version 3.0.
+ *
+ * While {@link #escapeHtml3(String)} is the expected method of use, this
+ * object allows the HTML escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator ESCAPE_HTML3 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE())
+ );
+
+ /**
+ * Translator object for escaping HTML version 4.0.
+ *
+ * While {@link #escapeHtml4(String)} is the expected method of use, this
+ * object allows the HTML escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator ESCAPE_HTML4 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
+ new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()),
+ new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE())
+ );
+
+ /**
+ * Translator object for escaping individual Comma Separated Values.
+ *
+ * While {@link #escapeCsv(String)} is the expected method of use, this
+ * object allows the CSV escaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ 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.
+ *
+ * While {@link #unescapeJava(String)} is the expected method of use, this
+ * object allows the Java unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ // TODO: throw "illegal character: \92" as an Exception if a \ on the end of the Java (as per the compiler)?
+ public static final CharSequenceTranslator UNESCAPE_JAVA =
+ new AggregateTranslator(
+ new OctalUnescaper(), // .between('\1', '\377'),
+ new UnicodeUnescaper(),
+ new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_UNESCAPE()),
+ new LookupTranslator(
+ new String[][] {
+ {"\\\\", "\\"},
+ {"\\\"", "\""},
+ {"\\'", "'"},
+ {"\\", ""}
+ })
+ );
+
+ /**
+ * Translator object for unescaping escaped EcmaScript.
+ *
+ * While {@link #unescapeEcmaScript(String)} is the expected method of use, this
+ * object allows the EcmaScript unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator UNESCAPE_ECMASCRIPT = UNESCAPE_JAVA;
+
+ /**
+ * Translator object for unescaping escaped Json.
+ *
+ * While {@link #unescapeJson(String)} is the expected method of use, this
+ * object allows the Json unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.2
+ */
+ public static final CharSequenceTranslator UNESCAPE_JSON = UNESCAPE_JAVA;
+
+ /**
+ * Translator object for unescaping escaped HTML 3.0.
+ *
+ * While {@link #unescapeHtml3(String)} is the expected method of use, this
+ * object allows the HTML unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator UNESCAPE_HTML3 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_UNESCAPE()),
+ new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
+ new NumericEntityUnescaper()
+ );
+
+ /**
+ * Translator object for unescaping escaped HTML 4.0.
+ *
+ * While {@link #unescapeHtml4(String)} is the expected method of use, this
+ * object allows the HTML unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator UNESCAPE_HTML4 =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_UNESCAPE()),
+ new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
+ new LookupTranslator(EntityArrays.HTML40_EXTENDED_UNESCAPE()),
+ new NumericEntityUnescaper()
+ );
+
+ /**
+ * Translator object for unescaping escaped XML.
+ *
+ * While {@link #unescapeXml(String)} is the expected method of use, this
+ * object allows the XML unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ public static final CharSequenceTranslator UNESCAPE_XML =
+ new AggregateTranslator(
+ new LookupTranslator(EntityArrays.BASIC_UNESCAPE()),
+ new LookupTranslator(EntityArrays.APOS_UNESCAPE()),
+ new NumericEntityUnescaper()
+ );
+
+ /**
+ * Translator object for unescaping escaped Comma Separated Value entries.
+ *
+ * While {@link #unescapeCsv(String)} is the expected method of use, this
+ * object allows the CSV unescaping functionality to be used
+ * as the foundation for a custom translator.
+ *
+ * @since 3.0
+ */
+ 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.
+ *
+ * <p>Instead, the class should be used as:</p>
+ * <pre>StringEscapeUtils.escapeJava("foo");</pre>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public StringEscapeUtils() {
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using Java String rules.
+ *
+ * <p>Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
+ *
+ * <p>So a tab becomes the characters {@code '\\'} and
+ * {@code 't'}.</p>
+ *
+ * <p>The only difference between Java strings and JavaScript strings
+ * is that in JavaScript, a single quote and forward-slash (/) are escaped.</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * input string: He didn't say, "Stop!"
+ * output string: He didn't say, \"Stop!\"
+ * </pre>
+ *
+ * @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);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using EcmaScript String rules.
+ * <p>Escapes any values it finds into their EcmaScript String form.
+ * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
+ *
+ * <p>So a tab becomes the characters {@code '\\'} and
+ * {@code 't'}.</p>
+ *
+ * <p>The only difference between Java strings and EcmaScript strings
+ * is that in EcmaScript, a single quote and forward-slash (/) are escaped.</p>
+ *
+ * <p>Note that EcmaScript is best known by the JavaScript and ActionScript dialects.</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * input string: He didn't say, "Stop!"
+ * output string: He didn\'t say, \"Stop!\"
+ * </pre>
+ *
+ * @param input String to escape values in, may be null
+ * @return String with escaped values, {@code null} if null string input
+ *
+ * @since 3.0
+ */
+ public static final String escapeEcmaScript(final String input) {
+ return ESCAPE_ECMASCRIPT.translate(input);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using Json String rules.
+ * <p>Escapes any values it finds into their Json String form.
+ * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
+ *
+ * <p>So a tab becomes the characters {@code '\\'} and
+ * {@code 't'}.</p>
+ *
+ * <p>The only difference between Java strings and Json strings
+ * is that in Json, forward-slash (/) is escaped.</p>
+ *
+ * <p>See https://www.ietf.org/rfc/rfc4627.txt for further details.</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * input string: He didn't say, "Stop!"
+ * output string: He didn't say, \"Stop!\"
+ * </pre>
+ *
+ * @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}.
+ *
+ * <p>For example, it will turn a sequence of {@code '\'} and {@code 'n'}
+ * into a newline character, unless the {@code '\'} is preceded by another
+ * {@code '\'}.</p>
+ *
+ * @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 any Json literals found in the {@link String}.
+ *
+ * <p>For example, it will turn a sequence of {@code '\'} and {@code 'n'}
+ * into a newline character, unless the {@code '\'} is preceded by another
+ * {@code '\'}.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using HTML entities.
+ *
+ * <p>
+ * For example:
+ * </p>
+ * <p>{@code "bread" &amp; "butter"}</p>
+ * becomes:
+ * <p>
+ * {@code &amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;}.
+ * </p>
+ *
+ * <p>Supports all known HTML 4.0 entities, including funky accents.
+ * Note that the commonly used apostrophe escape character (&amp;apos;)
+ * is not a legal entity and so is not supported).</p>
+ *
+ * @param input the {@link String} to escape, may be null
+ * @return a new escaped {@link String}, {@code null} if null string input
+ *
+ * @see <a href="https://web.archive.org/web/20060225074150/https://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
+ * @see <a href="https://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
+ * @see <a href="https://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
+ * @see <a href="https://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
+ * @see <a href="https://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
+ *
+ * @since 3.0
+ */
+ public static final String escapeHtml4(final String input) {
+ return ESCAPE_HTML4.translate(input);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using HTML entities.
+ * <p>Supports only the HTML 3.0 entities.</p>
+ *
+ * @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 escapeHtml3(final String input) {
+ return ESCAPE_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.
+ *
+ * <p>For example, the string {@code "&lt;Fran&ccedil;ais&gt;"}
+ * will become {@code "<Français>"}</p>
+ *
+ * <p>If an entity is unrecognized, it is left alone, and inserted
+ * verbatim into the result string. e.g. {@code "&gt;&zzzz;x"} will
+ * become {@code ">&zzzz;x"}.</p>
+ *
+ * @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 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);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using XML entities.
+ *
+ * <p>For example: {@code "bread" & "butter"} =&gt;
+ * {@code &quot;bread&quot; &amp; &quot;butter&quot;}.
+ * </p>
+ *
+ * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
+ * Does not support DTDs or external entities.</p>
+ *
+ * <p>Note that Unicode characters greater than 0x7f are as of 3.0, no longer
+ * escaped. If you still wish this functionality, you can achieve it
+ * via the following:
+ * {@code StringEscapeUtils.ESCAPE_XML.with( NumericEntityEscaper.between(0x7f, Integer.MAX_VALUE) );}</p>
+ *
+ * @param input the {@link String} to escape, may be null
+ * @return a new escaped {@link String}, {@code null} if null string input
+ * @see #unescapeXml(String)
+ * @deprecated use {@link #escapeXml10(java.lang.String)} or {@link #escapeXml11(java.lang.String)} instead.
+ */
+ @Deprecated
+ public static final String escapeXml(final String input) {
+ return ESCAPE_XML.translate(input);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using XML entities.
+ *
+ * <p>For example: {@code "bread" & "butter"} =&gt;
+ * {@code &quot;bread&quot; &amp; &quot;butter&quot;}.
+ * </p>
+ *
+ * <p>Note that XML 1.0 is a text-only format: it cannot represent control
+ * characters or unpaired Unicode surrogate code points, even after escaping.
+ * {@code escapeXml10} will remove characters that do not fit in the
+ * following ranges:</p>
+ *
+ * <p>{@code #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}</p>
+ *
+ * <p>Though not strictly necessary, {@code escapeXml10} will escape
+ * characters in the following ranges:</p>
+ *
+ * <p>{@code [#x7F-#x84] | [#x86-#x9F]}</p>
+ *
+ * <p>The returned string can be inserted into a valid XML 1.0 or XML 1.1
+ * document. If you want to allow more non-text characters in an XML 1.1
+ * document, use {@link #escapeXml11(String)}.</p>
+ *
+ * @param input the {@link String} to escape, may be null
+ * @return a new escaped {@link String}, {@code null} if null string input
+ * @see #unescapeXml(String)
+ * @since 3.3
+ */
+ public static String escapeXml10(final String input) {
+ return ESCAPE_XML10.translate(input);
+ }
+
+ /**
+ * Escapes the characters in a {@link String} using XML entities.
+ *
+ * <p>For example: {@code "bread" & "butter"} =&gt;
+ * {@code &quot;bread&quot; &amp; &quot;butter&quot;}.
+ * </p>
+ *
+ * <p>XML 1.1 can represent certain control characters, but it cannot represent
+ * the null byte or unpaired Unicode surrogate code points, even after escaping.
+ * {@code escapeXml11} will remove characters that do not fit in the following
+ * ranges:</p>
+ *
+ * <p>{@code [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]}</p>
+ *
+ * <p>{@code escapeXml11} will escape characters in the following ranges:</p>
+ *
+ * <p>{@code [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]}</p>
+ *
+ * <p>The returned string can be inserted into a valid XML 1.1 document. Do not
+ * use it for XML 1.0 documents.</p>
+ *
+ * @param input the {@link String} to escape, may be null
+ * @return a new escaped {@link String}, {@code null} if null string input
+ * @see #unescapeXml(String)
+ * @since 3.3
+ */
+ public static String escapeXml11(final String input) {
+ return ESCAPE_XML11.translate(input);
+ }
+
+ /**
+ * Unescapes a string containing XML entity escapes to a string
+ * containing the actual Unicode characters corresponding to the
+ * escapes.
+ *
+ * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
+ * Does not support DTDs or external entities.</p>
+ *
+ * <p>Note that numerical \\u Unicode codes are unescaped to their respective
+ * Unicode characters. This may change in future releases.</p>
+ *
+ * @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.
+ *
+ * <p>If the value contains a comma, newline or double quote, then the
+ * String value is returned enclosed in double quotes.</p>
+ *
+ * <p>Any double quote characters in the value are escaped with another double quote.</p>
+ *
+ * <p>If the value does not contain a comma, newline or double quote, then the
+ * String value is returned unchanged.</p>
+ *
+ * see <a href="https://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
+ * <a href="https://datatracker.ietf.org/doc/html/rfc4180">RFC 4180</a>.
+ *
+ * @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.
+ *
+ * <p>If the value is enclosed in double quotes, and contains a comma, newline
+ * or double quote, then quotes are removed.
+ * </p>
+ *
+ * <p>Any double quote escaped characters (a pair of double quotes) are unescaped
+ * to just one double quote.</p>
+ *
+ * <p>If the value is not enclosed in double quotes, or is and does not contain a
+ * comma, newline or double quote, then the String value is returned unchanged.</p>
+ *
+ * see <a href="https://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
+ * <a href="https://datatracker.ietf.org/doc/html/rfc4180">RFC 4180</a>.
+ *
+ * @param input the input CSV column String, may be null
+ * @return the input String, with enclosing double quotes removed and embedded double
+ * quotes unescaped, {@code null} if null string input
+ * @since 2.4
+ */
+ public static final String unescapeCsv(final String input) {
+ return UNESCAPE_CSV.translate(input);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java
new file mode 100644
index 000000000..9f2b18f50
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/StringUtils.java
@@ -0,0 +1,9549 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.text.Normalizer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.function.Suppliers;
+import org.apache.commons.lang3.function.ToBooleanBiFunction;
+import org.apache.commons.lang3.stream.LangCollectors;
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * Operations on {@link java.lang.String} that are
+ * {@code null} safe.
+ *
+ * <ul>
+ * <li><b>IsEmpty/IsBlank</b>
+ * - checks if a String contains text</li>
+ * <li><b>Trim/Strip</b>
+ * - removes leading and trailing whitespace</li>
+ * <li><b>Equals/Compare</b>
+ * - compares two strings in a null-safe manner</li>
+ * <li><b>startsWith</b>
+ * - check if a String starts with a prefix in a null-safe manner</li>
+ * <li><b>endsWith</b>
+ * - check if a String ends with a suffix in a null-safe manner</li>
+ * <li><b>IndexOf/LastIndexOf/Contains</b>
+ * - null-safe index-of checks
+ * <li><b>IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut</b>
+ * - index-of any of a set of Strings</li>
+ * <li><b>ContainsOnly/ContainsNone/ContainsAny</b>
+ * - checks if String contains only/none/any of these characters</li>
+ * <li><b>Substring/Left/Right/Mid</b>
+ * - null-safe substring extractions</li>
+ * <li><b>SubstringBefore/SubstringAfter/SubstringBetween</b>
+ * - substring extraction relative to other strings</li>
+ * <li><b>Split/Join</b>
+ * - splits a String into an array of substrings and vice versa</li>
+ * <li><b>Remove/Delete</b>
+ * - removes part of a String</li>
+ * <li><b>Replace/Overlay</b>
+ * - Searches a String and replaces one String with another</li>
+ * <li><b>Chomp/Chop</b>
+ * - removes the last part of a String</li>
+ * <li><b>AppendIfMissing</b>
+ * - appends a suffix to the end of the String if not present</li>
+ * <li><b>PrependIfMissing</b>
+ * - prepends a prefix to the start of the String if not present</li>
+ * <li><b>LeftPad/RightPad/Center/Repeat</b>
+ * - pads a String</li>
+ * <li><b>UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize</b>
+ * - changes the case of a String</li>
+ * <li><b>CountMatches</b>
+ * - counts the number of occurrences of one String in another</li>
+ * <li><b>IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable</b>
+ * - checks the characters in a String</li>
+ * <li><b>DefaultString</b>
+ * - protects against a null input String</li>
+ * <li><b>Rotate</b>
+ * - rotate (circular shift) a String</li>
+ * <li><b>Reverse/ReverseDelimited</b>
+ * - reverses a String</li>
+ * <li><b>Abbreviate</b>
+ * - abbreviates a string using ellipses or another given String</li>
+ * <li><b>Difference</b>
+ * - compares Strings and reports on their differences</li>
+ * <li><b>LevenshteinDistance</b>
+ * - the number of changes needed to change one String into another</li>
+ * </ul>
+ *
+ * <p>The {@link StringUtils} class defines certain words related to
+ * String handling.</p>
+ *
+ * <ul>
+ * <li>null - {@code null}</li>
+ * <li>empty - a zero-length string ({@code ""})</li>
+ * <li>space - the space character ({@code ' '}, char 32)</li>
+ * <li>whitespace - the characters defined by {@link Character#isWhitespace(char)}</li>
+ * <li>trim - the characters &lt;= 32 as in {@link String#trim()}</li>
+ * </ul>
+ *
+ * <p>{@link StringUtils} handles {@code null} input Strings quietly.
+ * That is to say that a {@code null} input will return {@code null}.
+ * Where a {@code boolean} or {@code int} is being returned
+ * details vary by method.</p>
+ *
+ * <p>A side effect of the {@code null} handling is that a
+ * {@link NullPointerException} should be considered a bug in
+ * {@link StringUtils}.</p>
+ *
+ * <p>Methods in this class include sample code in their Javadoc comments to explain their operation.
+ * The symbol {@code *} is used to indicate any input including {@code null}.</p>
+ *
+ * <p>#ThreadSafe#</p>
+ * @see String
+ * @since 1.0
+ */
+//@Immutable
+public class StringUtils {
+
+ // Performance testing notes (JDK 1.4, Jul03, scolebourne)
+ // Whitespace:
+ // Character.isWhitespace() is faster than WHITESPACE.indexOf()
+ // where WHITESPACE is a string of all whitespace characters
+ //
+ // Character access:
+ // String.charAt(n) versus toCharArray(), then array[n]
+ // String.charAt(n) is about 15% worse for a 10K string
+ // They are about equal for a length 50 string
+ // String.charAt(n) is about 4 times better for a length 3 string
+ // String.charAt(n) is best bet overall
+ //
+ // Append:
+ // String.concat about twice as fast as StringBuffer.append
+ // (not sure who tested this)
+
+ /**
+ * A String for a space character.
+ *
+ * @since 3.2
+ */
+ public static final String SPACE = " ";
+
+ /**
+ * The empty String {@code ""}.
+ * @since 2.0
+ */
+ public static final String EMPTY = "";
+
+ /**
+ * A String for linefeed LF ("\n").
+ *
+ * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 3.2
+ */
+ public static final String LF = "\n";
+
+ /**
+ * A String for carriage return CR ("\r").
+ *
+ * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+ * for Character and String Literals</a>
+ * @since 3.2
+ */
+ public static final String CR = "\r";
+
+ /**
+ * Represents a failed index search.
+ * @since 2.1
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * The maximum size to which the padding constant(s) can expand.
+ */
+ private static final int PAD_LIMIT = 8192;
+
+ /**
+ * Pattern used in {@link #stripAccents(String)}.
+ */
+ private static final Pattern STRIP_ACCENTS_PATTERN = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); //$NON-NLS-1$
+
+ /**
+ * Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "Now is the time for..."
+ *
+ * <p>Specifically:</p>
+ * <ul>
+ * <li>If the number of characters in {@code str} is less than or equal to
+ * {@code maxWidth}, return {@code str}.</li>
+ * <li>Else abbreviate it to {@code (substring(str, 0, max-3) + "...")}.</li>
+ * <li>If {@code maxWidth} is less than {@code 4}, throw an
+ * {@link IllegalArgumentException}.</li>
+ * <li>In no case will it return a String of length greater than
+ * {@code maxWidth}.</li>
+ * </ul>
+ *
+ * <pre>
+ * StringUtils.abbreviate(null, *) = null
+ * StringUtils.abbreviate("", 4) = ""
+ * StringUtils.abbreviate("abcdefg", 6) = "abc..."
+ * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", 4) = "a..."
+ * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to check, may be null
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(final String str, final int maxWidth) {
+ return abbreviate(str, "...", 0, maxWidth);
+ }
+
+ /**
+ * Abbreviates a String using ellipses. This will turn
+ * "Now is the time for all good men" into "...is the time for..."
+ *
+ * <p>Works like {@code abbreviate(String, int)}, but allows you to specify
+ * a "left edge" offset. Note that this left edge is not necessarily going to
+ * be the leftmost character in the result, or the first character following the
+ * ellipses, but it will appear somewhere in the result.
+ *
+ * <p>In no case will it return a String of length greater than
+ * {@code maxWidth}.</p>
+ *
+ * <pre>
+ * StringUtils.abbreviate(null, *, *) = null
+ * StringUtils.abbreviate("", 0, 4) = ""
+ * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 0, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 1, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 4, 10) = "abcdefg..."
+ * StringUtils.abbreviate("abcdefghijklmno", 5, 10) = "...fghi..."
+ * StringUtils.abbreviate("abcdefghijklmno", 6, 10) = "...ghij..."
+ * StringUtils.abbreviate("abcdefghijklmno", 8, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
+ * StringUtils.abbreviate("abcdefghij", 0, 3) = IllegalArgumentException
+ * StringUtils.abbreviate("abcdefghij", 5, 6) = IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to check, may be null
+ * @param offset left edge of source String
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 2.0
+ */
+ public static String abbreviate(final String str, final int offset, final int maxWidth) {
+ return abbreviate(str, "...", offset, maxWidth);
+ }
+
+ /**
+ * Abbreviates a String using another given String as replacement marker. This will turn
+ * "Now is the time for all good men" into "Now is the time for..." if "..." was defined
+ * as the replacement marker.
+ *
+ * <p>Specifically:</p>
+ * <ul>
+ * <li>If the number of characters in {@code str} is less than or equal to
+ * {@code maxWidth}, return {@code str}.</li>
+ * <li>Else abbreviate it to {@code (substring(str, 0, max-abbrevMarker.length) + abbrevMarker)}.</li>
+ * <li>If {@code maxWidth} is less than {@code abbrevMarker.length + 1}, throw an
+ * {@link IllegalArgumentException}.</li>
+ * <li>In no case will it return a String of length greater than
+ * {@code maxWidth}.</li>
+ * </ul>
+ *
+ * <pre>
+ * StringUtils.abbreviate(null, "...", *) = null
+ * StringUtils.abbreviate("abcdefg", null, *) = "abcdefg"
+ * StringUtils.abbreviate("", "...", 4) = ""
+ * StringUtils.abbreviate("abcdefg", ".", 5) = "abcd."
+ * StringUtils.abbreviate("abcdefg", ".", 7) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", ".", 8) = "abcdefg"
+ * StringUtils.abbreviate("abcdefg", "..", 4) = "ab.."
+ * StringUtils.abbreviate("abcdefg", "..", 3) = "a.."
+ * StringUtils.abbreviate("abcdefg", "..", 2) = IllegalArgumentException
+ * StringUtils.abbreviate("abcdefg", "...", 3) = IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to check, may be null
+ * @param abbrevMarker the String used as replacement marker
+ * @param maxWidth maximum length of result String, must be at least {@code abbrevMarker.length + 1}
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 3.6
+ */
+ public static String abbreviate(final String str, final String abbrevMarker, final int maxWidth) {
+ return abbreviate(str, abbrevMarker, 0, maxWidth);
+ }
+ /**
+ * Abbreviates a String using a given replacement marker. This will turn
+ * "Now is the time for all good men" into "...is the time for..." if "..." was defined
+ * as the replacement marker.
+ *
+ * <p>Works like {@code abbreviate(String, String, int)}, but allows you to specify
+ * a "left edge" offset. Note that this left edge is not necessarily going to
+ * be the leftmost character in the result, or the first character following the
+ * replacement marker, but it will appear somewhere in the result.
+ *
+ * <p>In no case will it return a String of length greater than {@code maxWidth}.</p>
+ *
+ * <pre>
+ * StringUtils.abbreviate(null, null, *, *) = null
+ * StringUtils.abbreviate("abcdefghijklmno", null, *, *) = "abcdefghijklmno"
+ * StringUtils.abbreviate("", "...", 0, 4) = ""
+ * StringUtils.abbreviate("abcdefghijklmno", "---", -1, 10) = "abcdefg---"
+ * StringUtils.abbreviate("abcdefghijklmno", ",", 0, 10) = "abcdefghi,"
+ * StringUtils.abbreviate("abcdefghijklmno", ",", 1, 10) = "abcdefghi,"
+ * StringUtils.abbreviate("abcdefghijklmno", ",", 2, 10) = "abcdefghi,"
+ * StringUtils.abbreviate("abcdefghijklmno", "::", 4, 10) = "::efghij::"
+ * StringUtils.abbreviate("abcdefghijklmno", "...", 6, 10) = "...ghij..."
+ * StringUtils.abbreviate("abcdefghijklmno", "*", 9, 10) = "*ghijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", "'", 10, 10) = "'ghijklmno"
+ * StringUtils.abbreviate("abcdefghijklmno", "!", 12, 10) = "!ghijklmno"
+ * StringUtils.abbreviate("abcdefghij", "abra", 0, 4) = IllegalArgumentException
+ * StringUtils.abbreviate("abcdefghij", "...", 5, 6) = IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to check, may be null
+ * @param abbrevMarker the String used as replacement marker
+ * @param offset left edge of source String
+ * @param maxWidth maximum length of result String, must be at least 4
+ * @return abbreviated String, {@code null} if null String input
+ * @throws IllegalArgumentException if the width is too small
+ * @since 3.6
+ */
+ public static String abbreviate(final String str, final String abbrevMarker, int offset, final int maxWidth) {
+ if (isNotEmpty(str) && EMPTY.equals(abbrevMarker) && maxWidth > 0) {
+ return substring(str, 0, maxWidth);
+ }
+ if (isAnyEmpty(str, abbrevMarker)) {
+ return str;
+ }
+ final int abbrevMarkerLength = abbrevMarker.length();
+ final int minAbbrevWidth = abbrevMarkerLength + 1;
+ final int minAbbrevWidthOffset = abbrevMarkerLength + abbrevMarkerLength + 1;
+
+ if (maxWidth < minAbbrevWidth) {
+ throw new IllegalArgumentException(String.format("Minimum abbreviation width is %d", minAbbrevWidth));
+ }
+ final int strLen = str.length();
+ if (strLen <= maxWidth) {
+ return str;
+ }
+ if (offset > strLen) {
+ offset = strLen;
+ }
+ if (strLen - offset < maxWidth - abbrevMarkerLength) {
+ offset = strLen - (maxWidth - abbrevMarkerLength);
+ }
+ if (offset <= abbrevMarkerLength+1) {
+ return str.substring(0, maxWidth - abbrevMarkerLength) + abbrevMarker;
+ }
+ if (maxWidth < minAbbrevWidthOffset) {
+ throw new IllegalArgumentException(String.format("Minimum abbreviation width with offset is %d", minAbbrevWidthOffset));
+ }
+ if (offset + maxWidth - abbrevMarkerLength < strLen) {
+ return abbrevMarker + abbreviate(str.substring(offset), abbrevMarker, maxWidth - abbrevMarkerLength);
+ }
+ return abbrevMarker + str.substring(strLen - (maxWidth - abbrevMarkerLength));
+ }
+
+ /**
+ * Abbreviates a String to the length passed, replacing the middle characters with the supplied
+ * replacement String.
+ *
+ * <p>This abbreviation only occurs if the following criteria is met:</p>
+ * <ul>
+ * <li>Neither the String for abbreviation nor the replacement String are null or empty </li>
+ * <li>The length to truncate to is less than the length of the supplied String</li>
+ * <li>The length to truncate to is greater than 0</li>
+ * <li>The abbreviated String will have enough room for the length supplied replacement String
+ * and the first and last characters of the supplied String for abbreviation</li>
+ * </ul>
+ * <p>Otherwise, the returned String will be the same as the supplied String for abbreviation.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.abbreviateMiddle(null, null, 0) = null
+ * StringUtils.abbreviateMiddle("abc", null, 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 0) = "abc"
+ * StringUtils.abbreviateMiddle("abc", ".", 3) = "abc"
+ * StringUtils.abbreviateMiddle("abcdef", ".", 4) = "ab.f"
+ * </pre>
+ *
+ * @param str the String to abbreviate, may be null
+ * @param middle the String to replace the middle characters with, may be null
+ * @param length the length to abbreviate {@code str} to.
+ * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation.
+ * @since 2.5
+ */
+ public static String abbreviateMiddle(final String str, final String middle, final int length) {
+ if (isAnyEmpty(str, middle) || length >= str.length() || length < middle.length()+2) {
+ return str;
+ }
+
+ final int targetSting = length-middle.length();
+ final int startOffset = targetSting/2+targetSting%2;
+ final int endOffset = str.length()-targetSting/2;
+
+ return str.substring(0, startOffset) +
+ middle +
+ str.substring(endOffset);
+ }
+
+ /**
+ * Appends the suffix to the end of the string if the string does not
+ * already end with the suffix.
+ *
+ * @param str The string.
+ * @param suffix The suffix to append to the end of the string.
+ * @param ignoreCase Indicates whether the compare should ignore case.
+ * @param suffixes Additional suffixes that are valid terminators (optional).
+ *
+ * @return A new String if suffix was appended, the same string otherwise.
+ */
+ private static String appendIfMissing(final String str, final CharSequence suffix, final boolean ignoreCase, final CharSequence... suffixes) {
+ if (str == null || isEmpty(suffix) || endsWith(str, suffix, ignoreCase)) {
+ return str;
+ }
+ if (ArrayUtils.isNotEmpty(suffixes)) {
+ for (final CharSequence s : suffixes) {
+ if (endsWith(str, s, ignoreCase)) {
+ return str;
+ }
+ }
+ }
+ return str + suffix.toString();
+ }
+
+ /**
+ * Appends the suffix to the end of the string if the string does not
+ * already end with any of the suffixes.
+ *
+ * <pre>
+ * StringUtils.appendIfMissing(null, null) = null
+ * StringUtils.appendIfMissing("abc", null) = "abc"
+ * StringUtils.appendIfMissing("", "xyz") = "xyz"
+ * StringUtils.appendIfMissing("abc", "xyz") = "abcxyz"
+ * StringUtils.appendIfMissing("abcxyz", "xyz") = "abcxyz"
+ * StringUtils.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz"
+ * </pre>
+ * <p>With additional suffixes,</p>
+ * <pre>
+ * StringUtils.appendIfMissing(null, null, null) = null
+ * StringUtils.appendIfMissing("abc", null, null) = "abc"
+ * StringUtils.appendIfMissing("", "xyz", null) = "xyz"
+ * StringUtils.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
+ * StringUtils.appendIfMissing("abc", "xyz", "") = "abc"
+ * StringUtils.appendIfMissing("abc", "xyz", "mno") = "abcxyz"
+ * StringUtils.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
+ * StringUtils.appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
+ * StringUtils.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz"
+ * StringUtils.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz"
+ * </pre>
+ *
+ * @param str The string.
+ * @param suffix The suffix to append to the end of the string.
+ * @param suffixes Additional suffixes that are valid terminators.
+ *
+ * @return A new String if suffix was appended, the same string otherwise.
+ *
+ * @since 3.2
+ */
+ public static String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) {
+ return appendIfMissing(str, suffix, false, suffixes);
+ }
+
+ /**
+ * Appends the suffix to the end of the string if the string does not
+ * already end, case-insensitive, with any of the suffixes.
+ *
+ * <pre>
+ * StringUtils.appendIfMissingIgnoreCase(null, null) = null
+ * StringUtils.appendIfMissingIgnoreCase("abc", null) = "abc"
+ * StringUtils.appendIfMissingIgnoreCase("", "xyz") = "xyz"
+ * StringUtils.appendIfMissingIgnoreCase("abc", "xyz") = "abcxyz"
+ * StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz") = "abcxyz"
+ * StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz") = "abcXYZ"
+ * </pre>
+ * <p>With additional suffixes,</p>
+ * <pre>
+ * StringUtils.appendIfMissingIgnoreCase(null, null, null) = null
+ * StringUtils.appendIfMissingIgnoreCase("abc", null, null) = "abc"
+ * StringUtils.appendIfMissingIgnoreCase("", "xyz", null) = "xyz"
+ * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
+ * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", "") = "abc"
+ * StringUtils.appendIfMissingIgnoreCase("abc", "xyz", "mno") = "abcxyz"
+ * StringUtils.appendIfMissingIgnoreCase("abcxyz", "xyz", "mno") = "abcxyz"
+ * StringUtils.appendIfMissingIgnoreCase("abcmno", "xyz", "mno") = "abcmno"
+ * StringUtils.appendIfMissingIgnoreCase("abcXYZ", "xyz", "mno") = "abcXYZ"
+ * StringUtils.appendIfMissingIgnoreCase("abcMNO", "xyz", "mno") = "abcMNO"
+ * </pre>
+ *
+ * @param str The string.
+ * @param suffix The suffix to append to the end of the string.
+ * @param suffixes Additional suffixes that are valid terminators.
+ *
+ * @return A new String if suffix was appended, the same string otherwise.
+ *
+ * @since 3.2
+ */
+ public static String appendIfMissingIgnoreCase(final String str, final CharSequence suffix, final CharSequence... suffixes) {
+ return appendIfMissing(str, suffix, true, suffixes);
+ }
+
+ /**
+ * Capitalizes a String changing the first character to title case as
+ * per {@link Character#toTitleCase(int)}. No other characters are changed.
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.text.WordUtils#capitalize(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.capitalize(null) = null
+ * StringUtils.capitalize("") = ""
+ * StringUtils.capitalize("cat") = "Cat"
+ * StringUtils.capitalize("cAt") = "CAt"
+ * StringUtils.capitalize("'cat'") = "'cat'"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, {@code null} if null String input
+ * @see org.apache.commons.text.WordUtils#capitalize(String)
+ * @see #uncapitalize(String)
+ * @since 2.0
+ */
+ public static String capitalize(final String str) {
+ final int strLen = length(str);
+ if (strLen == 0) {
+ return str;
+ }
+
+ final int firstCodepoint = str.codePointAt(0);
+ final int newCodePoint = Character.toTitleCase(firstCodepoint);
+ if (firstCodepoint == newCodePoint) {
+ // already capitalized
+ return str;
+ }
+
+ final int[] newCodePoints = new int[strLen]; // cannot be longer than the char array
+ int outOffset = 0;
+ newCodePoints[outOffset++] = newCodePoint; // copy the first code point
+ for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; ) {
+ final int codePoint = str.codePointAt(inOffset);
+ newCodePoints[outOffset++] = codePoint; // copy the remaining ones
+ inOffset += Character.charCount(codePoint);
+ }
+ return new String(newCodePoints, 0, outOffset);
+ }
+
+ /**
+ * Centers a String in a larger String of size {@code size}
+ * using the space character (' ').
+ *
+ * <p>If the size is less than the String length, the original String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.</p>
+ *
+ * <p>Equivalent to {@code center(str, size, " ")}.</p>
+ *
+ * <pre>
+ * StringUtils.center(null, *) = null
+ * StringUtils.center("", 4) = " "
+ * StringUtils.center("ab", -1) = "ab"
+ * StringUtils.center("ab", 4) = " ab "
+ * StringUtils.center("abcd", 2) = "abcd"
+ * StringUtils.center("a", 4) = " a "
+ * </pre>
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @return centered String, {@code null} if null String input
+ */
+ public static String center(final String str, final int size) {
+ return center(str, size, ' ');
+ }
+
+ /**
+ * Centers a String in a larger String of size {@code size}.
+ * Uses a supplied character as the value to pad the String with.
+ *
+ * <p>If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.</p>
+ *
+ * <pre>
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, ' ') = " "
+ * StringUtils.center("ab", -1, ' ') = "ab"
+ * StringUtils.center("ab", 4, ' ') = " ab "
+ * StringUtils.center("abcd", 2, ' ') = "abcd"
+ * StringUtils.center("a", 4, ' ') = " a "
+ * StringUtils.center("a", 4, 'y') = "yayy"
+ * </pre>
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padChar the character to pad the new String with
+ * @return centered String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String center(String str, final int size, final char padChar) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padChar);
+ str = rightPad(str, size, padChar);
+ return str;
+ }
+
+ /**
+ * Centers a String in a larger String of size {@code size}.
+ * Uses a supplied String as the value to pad the String with.
+ *
+ * <p>If the size is less than the String length, the String is returned.
+ * A {@code null} String returns {@code null}.
+ * A negative size is treated as zero.</p>
+ *
+ * <pre>
+ * StringUtils.center(null, *, *) = null
+ * StringUtils.center("", 4, " ") = " "
+ * StringUtils.center("ab", -1, " ") = "ab"
+ * StringUtils.center("ab", 4, " ") = " ab "
+ * StringUtils.center("abcd", 2, " ") = "abcd"
+ * StringUtils.center("a", 4, " ") = " a "
+ * StringUtils.center("a", 4, "yz") = "yayz"
+ * StringUtils.center("abc", 7, null) = " abc "
+ * StringUtils.center("abc", 7, "") = " abc "
+ * </pre>
+ *
+ * @param str the String to center, may be null
+ * @param size the int size of new String, negative treated as zero
+ * @param padStr the String to pad the new String with, must not be null or empty
+ * @return centered String, {@code null} if null String input
+ * @throws IllegalArgumentException if padStr is {@code null} or empty
+ */
+ public static String center(String str, final int size, String padStr) {
+ if (str == null || size <= 0) {
+ return str;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ }
+ str = leftPad(str, strLen + pads / 2, padStr);
+ str = rightPad(str, size, padStr);
+ return str;
+ }
+
+ /**
+ * Removes one newline from end of a String if it's there,
+ * otherwise leave it alone. A newline is &quot;{@code \n}&quot;,
+ * &quot;{@code \r}&quot;, or &quot;{@code \r\n}&quot;.
+ *
+ * <p>NOTE: This method changed in 2.0.
+ * It now more closely matches Perl chomp.</p>
+ *
+ * <pre>
+ * StringUtils.chomp(null) = null
+ * StringUtils.chomp("") = ""
+ * StringUtils.chomp("abc \r") = "abc "
+ * StringUtils.chomp("abc\n") = "abc"
+ * StringUtils.chomp("abc\r\n") = "abc"
+ * StringUtils.chomp("abc\r\n\r\n") = "abc\r\n"
+ * StringUtils.chomp("abc\n\r") = "abc\n"
+ * StringUtils.chomp("abc\n\rabc") = "abc\n\rabc"
+ * StringUtils.chomp("\r") = ""
+ * StringUtils.chomp("\n") = ""
+ * StringUtils.chomp("\r\n") = ""
+ * </pre>
+ *
+ * @param str the String to chomp a newline from, may be null
+ * @return String without newline, {@code null} if null String input
+ */
+ public static String chomp(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+
+ if (str.length() == 1) {
+ final char ch = str.charAt(0);
+ if (ch == CharUtils.CR || ch == CharUtils.LF) {
+ return EMPTY;
+ }
+ return str;
+ }
+
+ int lastIdx = str.length() - 1;
+ final char last = str.charAt(lastIdx);
+
+ if (last == CharUtils.LF) {
+ if (str.charAt(lastIdx - 1) == CharUtils.CR) {
+ lastIdx--;
+ }
+ } else if (last != CharUtils.CR) {
+ lastIdx++;
+ }
+ return str.substring(0, lastIdx);
+ }
+
+ /**
+ * Removes {@code separator} from the end of
+ * {@code str} if it's there, otherwise leave it alone.
+ *
+ * <p>NOTE: This method changed in version 2.0.
+ * It now more closely matches Perl chomp.
+ * For the previous behavior, use {@link #substringBeforeLast(String, String)}.
+ * This method uses {@link String#endsWith(String)}.</p>
+ *
+ * <pre>
+ * StringUtils.chomp(null, *) = null
+ * StringUtils.chomp("", *) = ""
+ * StringUtils.chomp("foobar", "bar") = "foo"
+ * StringUtils.chomp("foobar", "baz") = "foobar"
+ * StringUtils.chomp("foo", "foo") = ""
+ * StringUtils.chomp("foo ", "foo") = "foo "
+ * StringUtils.chomp(" foo", "foo") = " "
+ * StringUtils.chomp("foo", "foooo") = "foo"
+ * StringUtils.chomp("foo", "") = "foo"
+ * StringUtils.chomp("foo", null) = "foo"
+ * </pre>
+ *
+ * @param str the String to chomp from, may be null
+ * @param separator separator String, may be null
+ * @return String without trailing separator, {@code null} if null String input
+ * @deprecated This feature will be removed in Lang 4.0, use {@link StringUtils#removeEnd(String, String)} instead
+ */
+ @Deprecated
+ public static String chomp(final String str, final String separator) {
+ return removeEnd(str, separator);
+ }
+
+ /**
+ * Remove the last character from a String.
+ *
+ * <p>If the String ends in {@code \r\n}, then remove both
+ * of them.</p>
+ *
+ * <pre>
+ * StringUtils.chop(null) = null
+ * StringUtils.chop("") = ""
+ * StringUtils.chop("abc \r") = "abc "
+ * StringUtils.chop("abc\n") = "abc"
+ * StringUtils.chop("abc\r\n") = "abc"
+ * StringUtils.chop("abc") = "ab"
+ * StringUtils.chop("abc\nabc") = "abc\nab"
+ * StringUtils.chop("a") = ""
+ * StringUtils.chop("\r") = ""
+ * StringUtils.chop("\n") = ""
+ * StringUtils.chop("\r\n") = ""
+ * </pre>
+ *
+ * @param str the String to chop last character from, may be null
+ * @return String without last character, {@code null} if null String input
+ */
+ public static String chop(final String str) {
+ if (str == null) {
+ return null;
+ }
+ final int strLen = str.length();
+ if (strLen < 2) {
+ return EMPTY;
+ }
+ final int lastIdx = strLen - 1;
+ final String ret = str.substring(0, lastIdx);
+ final char last = str.charAt(lastIdx);
+ if (last == CharUtils.LF && ret.charAt(lastIdx - 1) == CharUtils.CR) {
+ return ret.substring(0, lastIdx - 1);
+ }
+ return ret;
+ }
+
+ /**
+ * Compare two Strings lexicographically, as per {@link String#compareTo(String)}, returning :
+ * <ul>
+ * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li>
+ * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li>
+ * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li>
+ * </ul>
+ *
+ * <p>This is a {@code null} safe version of :</p>
+ * <blockquote><pre>str1.compareTo(str2)</pre></blockquote>
+ *
+ * <p>{@code null} value is considered less than non-{@code null} value.
+ * Two {@code null} references are considered equal.</p>
+ *
+ * <pre>
+ * StringUtils.compare(null, null) = 0
+ * StringUtils.compare(null , "a") &lt; 0
+ * StringUtils.compare("a", null) &gt; 0
+ * StringUtils.compare("abc", "abc") = 0
+ * StringUtils.compare("a", "b") &lt; 0
+ * StringUtils.compare("b", "a") &gt; 0
+ * StringUtils.compare("a", "B") &gt; 0
+ * StringUtils.compare("ab", "abc") &lt; 0
+ * </pre>
+ *
+ * @see #compare(String, String, boolean)
+ * @see String#compareTo(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @return &lt; 0, 0, &gt; 0, if {@code str1} is respectively less, equal or greater than {@code str2}
+ * @since 3.5
+ */
+ public static int compare(final String str1, final String str2) {
+ return compare(str1, str2, true);
+ }
+
+ /**
+ * Compare two Strings lexicographically, as per {@link String#compareTo(String)}, returning :
+ * <ul>
+ * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li>
+ * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li>
+ * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li>
+ * </ul>
+ *
+ * <p>This is a {@code null} safe version of :</p>
+ * <blockquote><pre>str1.compareTo(str2)</pre></blockquote>
+ *
+ * <p>{@code null} inputs are handled according to the {@code nullIsLess} parameter.
+ * Two {@code null} references are considered equal.</p>
+ *
+ * <pre>
+ * StringUtils.compare(null, null, *) = 0
+ * StringUtils.compare(null , "a", true) &lt; 0
+ * StringUtils.compare(null , "a", false) &gt; 0
+ * StringUtils.compare("a", null, true) &gt; 0
+ * StringUtils.compare("a", null, false) &lt; 0
+ * StringUtils.compare("abc", "abc", *) = 0
+ * StringUtils.compare("a", "b", *) &lt; 0
+ * StringUtils.compare("b", "a", *) &gt; 0
+ * StringUtils.compare("a", "B", *) &gt; 0
+ * StringUtils.compare("ab", "abc", *) &lt; 0
+ * </pre>
+ *
+ * @see String#compareTo(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @param nullIsLess whether consider {@code null} value less than non-{@code null} value
+ * @return &lt; 0, 0, &gt; 0, if {@code str1} is respectively less, equal ou greater than {@code str2}
+ * @since 3.5
+ */
+ public static int compare(final String str1, final String str2, final boolean nullIsLess) {
+ if (str1 == str2) { // NOSONARLINT this intentionally uses == to allow for both null
+ return 0;
+ }
+ if (str1 == null) {
+ return nullIsLess ? -1 : 1;
+ }
+ if (str2 == null) {
+ return nullIsLess ? 1 : - 1;
+ }
+ return str1.compareTo(str2);
+ }
+
+ /**
+ * Compare two Strings lexicographically, ignoring case differences,
+ * as per {@link String#compareToIgnoreCase(String)}, returning :
+ * <ul>
+ * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li>
+ * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li>
+ * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li>
+ * </ul>
+ *
+ * <p>This is a {@code null} safe version of :</p>
+ * <blockquote><pre>str1.compareToIgnoreCase(str2)</pre></blockquote>
+ *
+ * <p>{@code null} value is considered less than non-{@code null} value.
+ * Two {@code null} references are considered equal.
+ * Comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.compareIgnoreCase(null, null) = 0
+ * StringUtils.compareIgnoreCase(null , "a") &lt; 0
+ * StringUtils.compareIgnoreCase("a", null) &gt; 0
+ * StringUtils.compareIgnoreCase("abc", "abc") = 0
+ * StringUtils.compareIgnoreCase("abc", "ABC") = 0
+ * StringUtils.compareIgnoreCase("a", "b") &lt; 0
+ * StringUtils.compareIgnoreCase("b", "a") &gt; 0
+ * StringUtils.compareIgnoreCase("a", "B") &lt; 0
+ * StringUtils.compareIgnoreCase("A", "b") &lt; 0
+ * StringUtils.compareIgnoreCase("ab", "ABC") &lt; 0
+ * </pre>
+ *
+ * @see #compareIgnoreCase(String, String, boolean)
+ * @see String#compareToIgnoreCase(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @return &lt; 0, 0, &gt; 0, if {@code str1} is respectively less, equal ou greater than {@code str2},
+ * ignoring case differences.
+ * @since 3.5
+ */
+ public static int compareIgnoreCase(final String str1, final String str2) {
+ return compareIgnoreCase(str1, str2, true);
+ }
+
+ /**
+ * Compare two Strings lexicographically, ignoring case differences,
+ * as per {@link String#compareToIgnoreCase(String)}, returning :
+ * <ul>
+ * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li>
+ * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li>
+ * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li>
+ * </ul>
+ *
+ * <p>This is a {@code null} safe version of :</p>
+ * <blockquote><pre>str1.compareToIgnoreCase(str2)</pre></blockquote>
+ *
+ * <p>{@code null} inputs are handled according to the {@code nullIsLess} parameter.
+ * Two {@code null} references are considered equal.
+ * Comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.compareIgnoreCase(null, null, *) = 0
+ * StringUtils.compareIgnoreCase(null , "a", true) &lt; 0
+ * StringUtils.compareIgnoreCase(null , "a", false) &gt; 0
+ * StringUtils.compareIgnoreCase("a", null, true) &gt; 0
+ * StringUtils.compareIgnoreCase("a", null, false) &lt; 0
+ * StringUtils.compareIgnoreCase("abc", "abc", *) = 0
+ * StringUtils.compareIgnoreCase("abc", "ABC", *) = 0
+ * StringUtils.compareIgnoreCase("a", "b", *) &lt; 0
+ * StringUtils.compareIgnoreCase("b", "a", *) &gt; 0
+ * StringUtils.compareIgnoreCase("a", "B", *) &lt; 0
+ * StringUtils.compareIgnoreCase("A", "b", *) &lt; 0
+ * StringUtils.compareIgnoreCase("ab", "abc", *) &lt; 0
+ * </pre>
+ *
+ * @see String#compareToIgnoreCase(String)
+ * @param str1 the String to compare from
+ * @param str2 the String to compare to
+ * @param nullIsLess whether consider {@code null} value less than non-{@code null} value
+ * @return &lt; 0, 0, &gt; 0, if {@code str1} is respectively less, equal ou greater than {@code str2},
+ * ignoring case differences.
+ * @since 3.5
+ */
+ public static int compareIgnoreCase(final String str1, final String str2, final boolean nullIsLess) {
+ if (str1 == str2) { // NOSONARLINT this intentionally uses == to allow for both null
+ return 0;
+ }
+ if (str1 == null) {
+ return nullIsLess ? -1 : 1;
+ }
+ if (str2 == null) {
+ return nullIsLess ? 1 : - 1;
+ }
+ return str1.compareToIgnoreCase(str2);
+ }
+
+ /**
+ * Checks if CharSequence contains a search CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains(*, null) = false
+ * StringUtils.contains("", "") = true
+ * StringUtils.contains("abc", "") = true
+ * StringUtils.contains("abc", "a") = true
+ * StringUtils.contains("abc", "z") = false
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return true if the CharSequence contains the search CharSequence,
+ * false if not or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from contains(String, String) to contains(CharSequence, CharSequence)
+ */
+ public static boolean contains(final CharSequence seq, final CharSequence searchSeq) {
+ if (seq == null || searchSeq == null) {
+ return false;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0;
+ }
+
+ /**
+ * Checks if CharSequence contains a search character, handling {@code null}.
+ * This method uses {@link String#indexOf(int)} if possible.
+ *
+ * <p>A {@code null} or empty ("") CharSequence will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains("", *) = false
+ * StringUtils.contains("abc", 'a') = true
+ * StringUtils.contains("abc", 'z') = false
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @return true if the CharSequence contains the search character,
+ * false if not or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int)
+ */
+ public static boolean contains(final CharSequence seq, final int searchChar) {
+ if (isEmpty(seq)) {
+ return false;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, 0) >= 0;
+ }
+
+ /**
+ * Checks if the CharSequence contains any character in the given
+ * set of characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} or zero length search array will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, []) = false
+ * StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
+ * StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
+ * StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
+ * StringUtils.containsAny("aba", ['z']) = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the {@code true} if any of the chars are found,
+ * {@code false} if no match or null input
+ * @since 2.4
+ * @since 3.0 Changed signature from containsAny(String, char[]) to containsAny(CharSequence, char...)
+ */
+ public static boolean containsAny(final CharSequence cs, final char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return false;
+ }
+ final int csLength = cs.length();
+ final int searchLength = searchChars.length;
+ final int csLast = csLength - 1;
+ final int searchLast = searchLength - 1;
+ for (int i = 0; i < csLength; i++) {
+ final char ch = cs.charAt(i);
+ for (int j = 0; j < searchLength; j++) {
+ if (searchChars[j] == ch) {
+ if (!Character.isHighSurrogate(ch)) {
+ // ch is in the Basic Multilingual Plane
+ return true;
+ }
+ if (j == searchLast) {
+ // missing low surrogate, fine, like String.indexOf(String)
+ return true;
+ }
+ if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the CharSequence contains any character in the given set of characters.
+ *
+ * <p>
+ * A {@code null} CharSequence will return {@code false}. A {@code null} search CharSequence will return
+ * {@code false}.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, "") = false
+ * StringUtils.containsAny("zzabyycdxx", "za") = true
+ * StringUtils.containsAny("zzabyycdxx", "by") = true
+ * StringUtils.containsAny("zzabyycdxx", "zy") = true
+ * StringUtils.containsAny("zzabyycdxx", "\tx") = true
+ * StringUtils.containsAny("zzabyycdxx", "$.#yF") = true
+ * StringUtils.containsAny("aba", "z") = false
+ * </pre>
+ *
+ * @param cs
+ * the CharSequence to check, may be null
+ * @param searchChars
+ * the chars to search for, may be null
+ * @return the {@code true} if any of the chars are found, {@code false} if no match or null input
+ * @since 2.4
+ * @since 3.0 Changed signature from containsAny(String, String) to containsAny(CharSequence, CharSequence)
+ */
+ public static boolean containsAny(final CharSequence cs, final CharSequence searchChars) {
+ if (searchChars == null) {
+ return false;
+ }
+ return containsAny(cs, CharSequenceUtils.toCharArray(searchChars));
+ }
+
+ /**
+ * Checks if the CharSequence contains any of the CharSequences in the given array.
+ *
+ * <p>
+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will
+ * return {@code false}.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, []) = false
+ * StringUtils.containsAny("abcd", "ab", null) = true
+ * StringUtils.containsAny("abcd", "ab", "cd") = true
+ * StringUtils.containsAny("abc", "d", "abc") = true
+ * </pre>
+ *
+ * @param cs The CharSequence to check, may be null
+ * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be
+ * null as well.
+ * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise
+ * @since 3.4
+ */
+ public static boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) {
+ return containsAny(StringUtils::contains, cs, searchCharSequences);
+ }
+
+ /**
+ * Checks if the CharSequence contains any of the CharSequences in the given array.
+ *
+ * <p>
+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will
+ * return {@code false}.
+ * </p>
+ *
+ * @param cs The CharSequence to check, may be null
+ * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be
+ * null as well.
+ * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise
+ * @since 3.12.0
+ */
+ private static boolean containsAny(final ToBooleanBiFunction<CharSequence, CharSequence> test,
+ final CharSequence cs, final CharSequence... searchCharSequences) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) {
+ return false;
+ }
+ for (final CharSequence searchCharSequence : searchCharSequences) {
+ if (test.applyAsBoolean(cs, searchCharSequence)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the CharSequence contains any of the CharSequences in the given array, ignoring case.
+ *
+ * <p>
+ * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will
+ * return {@code false}.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.containsAny(null, *) = false
+ * StringUtils.containsAny("", *) = false
+ * StringUtils.containsAny(*, null) = false
+ * StringUtils.containsAny(*, []) = false
+ * StringUtils.containsAny("abcd", "ab", null) = true
+ * StringUtils.containsAny("abcd", "ab", "cd") = true
+ * StringUtils.containsAny("abc", "d", "abc") = true
+ * StringUtils.containsAny("abc", "D", "ABC") = true
+ * StringUtils.containsAny("ABC", "d", "abc") = true
+ * </pre>
+ *
+ * @param cs The CharSequence to check, may be null
+ * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be
+ * null as well.
+ * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise
+ * @since 3.12.0
+ */
+ public static boolean containsAnyIgnoreCase(final CharSequence cs, final CharSequence... searchCharSequences) {
+ return containsAny(StringUtils::containsIgnoreCase, cs, searchCharSequences);
+ }
+
+ /**
+ * Checks if CharSequence contains a search CharSequence irrespective of case,
+ * handling {@code null}. Case-insensitivity is defined as by
+ * {@link String#equalsIgnoreCase(String)}.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ *
+ * <pre>
+ * StringUtils.containsIgnoreCase(null, *) = false
+ * StringUtils.containsIgnoreCase(*, null) = false
+ * StringUtils.containsIgnoreCase("", "") = true
+ * StringUtils.containsIgnoreCase("abc", "") = true
+ * StringUtils.containsIgnoreCase("abc", "a") = true
+ * StringUtils.containsIgnoreCase("abc", "z") = false
+ * StringUtils.containsIgnoreCase("abc", "A") = true
+ * StringUtils.containsIgnoreCase("abc", "Z") = false
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return true if the CharSequence contains the search CharSequence irrespective of
+ * case or false if not or {@code null} string input
+ * @since 3.0 Changed signature from containsIgnoreCase(String, String) to containsIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ final int len = searchStr.length();
+ final int max = str.length() - len;
+ for (int i = 0; i <= max; i++) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks that the CharSequence does not contain certain characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code true}.
+ * A {@code null} invalid character array will return {@code true}.
+ * An empty CharSequence (length()=0) always returns true.</p>
+ *
+ * <pre>
+ * StringUtils.containsNone(null, *) = true
+ * StringUtils.containsNone(*, null) = true
+ * StringUtils.containsNone("", *) = true
+ * StringUtils.containsNone("ab", '') = true
+ * StringUtils.containsNone("abab", 'xyz') = true
+ * StringUtils.containsNone("ab1", 'xyz') = true
+ * StringUtils.containsNone("abz", 'xyz') = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars an array of invalid chars, may be null
+ * @return true if it contains none of the invalid chars, or is null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsNone(String, char[]) to containsNone(CharSequence, char...)
+ */
+ public static boolean containsNone(final CharSequence cs, final char... searchChars) {
+ if (cs == null || searchChars == null) {
+ return true;
+ }
+ final int csLen = cs.length();
+ final int csLast = csLen - 1;
+ final int searchLen = searchChars.length;
+ final int searchLast = searchLen - 1;
+ for (int i = 0; i < csLen; i++) {
+ final char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (!Character.isHighSurrogate(ch)) {
+ // ch is in the Basic Multilingual Plane
+ return false;
+ }
+ if (j == searchLast) {
+ // missing low surrogate, fine, like String.indexOf(String)
+ return false;
+ }
+ if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks that the CharSequence does not contain certain characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code true}.
+ * A {@code null} invalid character array will return {@code true}.
+ * An empty String ("") always returns true.</p>
+ *
+ * <pre>
+ * StringUtils.containsNone(null, *) = true
+ * StringUtils.containsNone(*, null) = true
+ * StringUtils.containsNone("", *) = true
+ * StringUtils.containsNone("ab", "") = true
+ * StringUtils.containsNone("abab", "xyz") = true
+ * StringUtils.containsNone("ab1", "xyz") = true
+ * StringUtils.containsNone("abz", "xyz") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param invalidChars a String of invalid chars, may be null
+ * @return true if it contains none of the invalid chars, or is null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String)
+ */
+ public static boolean containsNone(final CharSequence cs, final String invalidChars) {
+ if (invalidChars == null) {
+ return true;
+ }
+ return containsNone(cs, invalidChars.toCharArray());
+ }
+
+ /**
+ * Checks if the CharSequence contains only certain characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} valid character array will return {@code false}.
+ * An empty CharSequence (length()=0) always returns {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.containsOnly(null, *) = false
+ * StringUtils.containsOnly(*, null) = false
+ * StringUtils.containsOnly("", *) = true
+ * StringUtils.containsOnly("ab", '') = false
+ * StringUtils.containsOnly("abab", 'abc') = true
+ * StringUtils.containsOnly("ab1", 'abc') = false
+ * StringUtils.containsOnly("abz", 'abc') = false
+ * </pre>
+ *
+ * @param cs the String to check, may be null
+ * @param valid an array of valid chars, may be null
+ * @return true if it only contains valid chars and is non-null
+ * @since 3.0 Changed signature from containsOnly(String, char[]) to containsOnly(CharSequence, char...)
+ */
+ public static boolean containsOnly(final CharSequence cs, final char... valid) {
+ // All these pre-checks are to maintain API with an older version
+ if (valid == null || cs == null) {
+ return false;
+ }
+ if (cs.length() == 0) {
+ return true;
+ }
+ if (valid.length == 0) {
+ return false;
+ }
+ return indexOfAnyBut(cs, valid) == INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if the CharSequence contains only certain characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code false}.
+ * A {@code null} valid character String will return {@code false}.
+ * An empty String (length()=0) always returns {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.containsOnly(null, *) = false
+ * StringUtils.containsOnly(*, null) = false
+ * StringUtils.containsOnly("", *) = true
+ * StringUtils.containsOnly("ab", "") = false
+ * StringUtils.containsOnly("abab", "abc") = true
+ * StringUtils.containsOnly("ab1", "abc") = false
+ * StringUtils.containsOnly("abz", "abc") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param validChars a String of valid chars, may be null
+ * @return true if it only contains valid chars and is non-null
+ * @since 2.0
+ * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, String)
+ */
+ public static boolean containsOnly(final CharSequence cs, final String validChars) {
+ if (cs == null || validChars == null) {
+ return false;
+ }
+ return containsOnly(cs, validChars.toCharArray());
+ }
+
+ /**
+ * Check whether the given CharSequence contains any whitespace characters.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * @param seq the CharSequence to check (may be {@code null})
+ * @return {@code true} if the CharSequence is not empty and
+ * contains at least 1 (breaking) whitespace character
+ * @since 3.0
+ */
+ // From org.springframework.util.StringUtils, under Apache License 2.0
+ public static boolean containsWhitespace(final CharSequence seq) {
+ if (isEmpty(seq)) {
+ return false;
+ }
+ final int strLen = seq.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(seq.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void convertRemainingAccentCharacters(final StringBuilder decomposed) {
+ for (int i = 0; i < decomposed.length(); i++) {
+ if (decomposed.charAt(i) == '\u0141') {
+ decomposed.setCharAt(i, 'L');
+ } else if (decomposed.charAt(i) == '\u0142') {
+ decomposed.setCharAt(i, 'l');
+ }
+ }
+ }
+
+ /**
+ * Counts how many times the char appears in the given string.
+ *
+ * <p>A {@code null} or empty ("") String input returns {@code 0}.</p>
+ *
+ * <pre>
+ * StringUtils.countMatches(null, *) = 0
+ * StringUtils.countMatches("", *) = 0
+ * StringUtils.countMatches("abba", 0) = 0
+ * StringUtils.countMatches("abba", 'a') = 2
+ * StringUtils.countMatches("abba", 'b') = 2
+ * StringUtils.countMatches("abba", 'x') = 0
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param ch the char to count
+ * @return the number of occurrences, 0 if the CharSequence is {@code null}
+ * @since 3.4
+ */
+ public static int countMatches(final CharSequence str, final char ch) {
+ if (isEmpty(str)) {
+ return 0;
+ }
+ int count = 0;
+ // We could also call str.toCharArray() for faster lookups but that would generate more garbage.
+ for (int i = 0; i < str.length(); i++) {
+ if (ch == str.charAt(i)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Counts how many times the substring appears in the larger string.
+ * Note that the code only counts non-overlapping matches.
+ *
+ * <p>A {@code null} or empty ("") String input returns {@code 0}.</p>
+ *
+ * <pre>
+ * StringUtils.countMatches(null, *) = 0
+ * StringUtils.countMatches("", *) = 0
+ * StringUtils.countMatches("abba", null) = 0
+ * StringUtils.countMatches("abba", "") = 0
+ * StringUtils.countMatches("abba", "a") = 2
+ * StringUtils.countMatches("abba", "ab") = 1
+ * StringUtils.countMatches("abba", "xxx") = 0
+ * StringUtils.countMatches("ababa", "aba") = 1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param sub the substring to count, may be null
+ * @return the number of occurrences, 0 if either CharSequence is {@code null}
+ * @since 3.0 Changed signature from countMatches(String, String) to countMatches(CharSequence, CharSequence)
+ */
+ public static int countMatches(final CharSequence str, final CharSequence sub) {
+ if (isEmpty(str) || isEmpty(sub)) {
+ return 0;
+ }
+ int count = 0;
+ int idx = 0;
+ while ((idx = CharSequenceUtils.indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
+ count++;
+ idx += sub.length();
+ }
+ return count;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.defaultIfBlank(null, "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank(" ", "NULL") = "NULL"
+ * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
+ * StringUtils.defaultIfBlank("", null) = null
+ * </pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is whitespace, empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static <T extends CharSequence> T defaultIfBlank(final T str, final T defaultStr) {
+ return isBlank(str) ? defaultStr : str;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * empty or {@code null}, the value of {@code defaultStr}.
+ *
+ * <pre>
+ * StringUtils.defaultIfEmpty(null, "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty("", "NULL") = "NULL"
+ * StringUtils.defaultIfEmpty(" ", "NULL") = " "
+ * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
+ * StringUtils.defaultIfEmpty("", null) = null
+ * </pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultStr the default CharSequence to return
+ * if the input is empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ */
+ public static <T extends CharSequence> T defaultIfEmpty(final T str, final T defaultStr) {
+ return isEmpty(str) ? defaultStr : str;
+ }
+
+ /**
+ * Returns either the passed in String,
+ * or if the String is {@code null}, an empty String ("").
+ *
+ * <pre>
+ * StringUtils.defaultString(null) = ""
+ * StringUtils.defaultString("") = ""
+ * StringUtils.defaultString("bat") = "bat"
+ * </pre>
+ *
+ * @see Objects#toString(Object, String)
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @return the passed in String, or the empty String if it
+ * was {@code null}
+ */
+ public static String defaultString(final String str) {
+ return Objects.toString(str, EMPTY);
+ }
+
+ /**
+ * Returns either the given String, or if the String is
+ * {@code null}, {@code nullDefault}.
+ *
+ * <pre>
+ * StringUtils.defaultString(null, "NULL") = "NULL"
+ * StringUtils.defaultString("", "NULL") = ""
+ * StringUtils.defaultString("bat", "NULL") = "bat"
+ * </pre>
+ *
+ * @see Objects#toString(Object, String)
+ * @see String#valueOf(Object)
+ * @param str the String to check, may be null
+ * @param nullDefault the default String to return
+ * if the input is {@code null}, may be null
+ * @return the passed in String, or the default if it was {@code null}
+ * @deprecated Use {@link Objects#toString(Object, String)}
+ */
+ @Deprecated
+ public static String defaultString(final String str, final String nullDefault) {
+ return Objects.toString(str, nullDefault);
+ }
+
+ /**
+ * Deletes all whitespaces from a String as defined by
+ * {@link Character#isWhitespace(char)}.
+ *
+ * <pre>
+ * StringUtils.deleteWhitespace(null) = null
+ * StringUtils.deleteWhitespace("") = ""
+ * StringUtils.deleteWhitespace("abc") = "abc"
+ * StringUtils.deleteWhitespace(" ab c ") = "abc"
+ * </pre>
+ *
+ * @param str the String to delete whitespace from, may be null
+ * @return the String without whitespaces, {@code null} if null String input
+ */
+ public static String deleteWhitespace(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int sz = str.length();
+ final char[] chs = new char[sz];
+ int count = 0;
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ chs[count++] = str.charAt(i);
+ }
+ }
+ if (count == sz) {
+ return str;
+ }
+ if (count == 0) {
+ return EMPTY;
+ }
+ return new String(chs, 0, count);
+ }
+
+ /**
+ * Compares two Strings, and returns the portion where they differ.
+ * More precisely, return the remainder of the second String,
+ * starting from where it's different from the first. This means that
+ * the difference between "abc" and "ab" is the empty String and not "c".
+ *
+ * <p>For example,
+ * {@code difference("i am a machine", "i am a robot") -> "robot"}.</p>
+ *
+ * <pre>
+ * StringUtils.difference(null, null) = null
+ * StringUtils.difference("", "") = ""
+ * StringUtils.difference("", "abc") = "abc"
+ * StringUtils.difference("abc", "") = ""
+ * StringUtils.difference("abc", "abc") = ""
+ * StringUtils.difference("abc", "ab") = ""
+ * StringUtils.difference("ab", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "abxyz") = "xyz"
+ * StringUtils.difference("abcde", "xyz") = "xyz"
+ * </pre>
+ *
+ * @param str1 the first String, may be null
+ * @param str2 the second String, may be null
+ * @return the portion of str2 where it differs from str1; returns the
+ * empty String if they are equal
+ * @see #indexOfDifference(CharSequence,CharSequence)
+ * @since 2.0
+ */
+ public static String difference(final String str1, final String str2) {
+ if (str1 == null) {
+ return str2;
+ }
+ if (str2 == null) {
+ return str1;
+ }
+ final int at = indexOfDifference(str1, str2);
+ if (at == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str2.substring(at);
+ }
+
+ /**
+ * Check if a CharSequence ends with a specified suffix.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case-sensitive.</p>
+ *
+ * <pre>
+ * StringUtils.endsWith(null, null) = true
+ * StringUtils.endsWith(null, "def") = false
+ * StringUtils.endsWith("abcdef", null) = false
+ * StringUtils.endsWith("abcdef", "def") = true
+ * StringUtils.endsWith("ABCDEF", "def") = false
+ * StringUtils.endsWith("ABCDEF", "cde") = false
+ * StringUtils.endsWith("ABCDEF", "") = true
+ * </pre>
+ *
+ * @see String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case-sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence)
+ */
+ public static boolean endsWith(final CharSequence str, final CharSequence suffix) {
+ return endsWith(str, suffix, false);
+ }
+
+ /**
+ * Check if a CharSequence ends with a specified suffix (optionally case insensitive).
+ *
+ * @see String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case-insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) {
+ if (str == null || suffix == null) {
+ return str == suffix;
+ }
+ if (suffix.length() > str.length()) {
+ return false;
+ }
+ final int strOffset = str.length() - suffix.length();
+ return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length());
+ }
+
+ /**
+ * Check if a CharSequence ends with any of the provided case-sensitive suffixes.
+ *
+ * <pre>
+ * StringUtils.endsWithAny(null, null) = false
+ * StringUtils.endsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.endsWithAny("abcxyz", null) = false
+ * StringUtils.endsWithAny("abcxyz", new String[] {""}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {"xyz"}) = true
+ * StringUtils.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ * StringUtils.endsWithAny("abcXYZ", "def", "XYZ") = true
+ * StringUtils.endsWithAny("abcXYZ", "def", "xyz") = false
+ * </pre>
+ *
+ * @param sequence the CharSequence to check, may be null
+ * @param searchStrings the case-sensitive CharSequences to find, may be empty or contain {@code null}
+ * @see StringUtils#endsWith(CharSequence, CharSequence)
+ * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or
+ * the input {@code sequence} ends in any of the provided case-sensitive {@code searchStrings}.
+ * @since 3.0
+ */
+ public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
+ if (isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (final CharSequence searchString : searchStrings) {
+ if (endsWith(sequence, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Case insensitive check if a CharSequence ends with a specified suffix.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.endsWithIgnoreCase(null, null) = true
+ * StringUtils.endsWithIgnoreCase(null, "def") = false
+ * StringUtils.endsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
+ * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
+ * </pre>
+ *
+ * @see String#endsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param suffix the suffix to find, may be null
+ * @return {@code true} if the CharSequence ends with the suffix, case-insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) {
+ return endsWith(str, suffix, true);
+ }
+
+ /**
+ * Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is <strong>case-sensitive</strong>.</p>
+ *
+ * <pre>
+ * StringUtils.equals(null, null) = true
+ * StringUtils.equals(null, "abc") = false
+ * StringUtils.equals("abc", null) = false
+ * StringUtils.equals("abc", "abc") = true
+ * StringUtils.equals("abc", "ABC") = false
+ * </pre>
+ *
+ * @param cs1 the first CharSequence, may be {@code null}
+ * @param cs2 the second CharSequence, may be {@code null}
+ * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
+ * @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence)
+ * @see Object#equals(Object)
+ * @see #equalsIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ if (cs1 instanceof String && cs2 instanceof String) {
+ return cs1.equals(cs2);
+ }
+ // Step-wise comparison
+ final int length = cs1.length();
+ for (int i = 0; i < length; i++) {
+ if (cs1.charAt(i) != cs2.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares given {@code string} to a CharSequences vararg of {@code searchStrings},
+ * returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}.
+ *
+ * <pre>
+ * StringUtils.equalsAny(null, (CharSequence[]) null) = false
+ * StringUtils.equalsAny(null, null, null) = true
+ * StringUtils.equalsAny(null, "abc", "def") = false
+ * StringUtils.equalsAny("abc", null, "def") = false
+ * StringUtils.equalsAny("abc", "abc", "def") = true
+ * StringUtils.equalsAny("abc", "ABC", "DEF") = false
+ * </pre>
+ *
+ * @param string to compare, may be {@code null}.
+ * @param searchStrings a vararg of strings, may be {@code null}.
+ * @return {@code true} if the string is equal (case-sensitive) to any other element of {@code searchStrings};
+ * {@code false} if {@code searchStrings} is null or contains no matches.
+ * @since 3.5
+ */
+ public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) {
+ if (ArrayUtils.isNotEmpty(searchStrings)) {
+ for (final CharSequence next : searchStrings) {
+ if (equals(string, next)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compares given {@code string} to a CharSequences vararg of {@code searchStrings},
+ * returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}, ignoring case.
+ *
+ * <pre>
+ * StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
+ * StringUtils.equalsAnyIgnoreCase(null, null, null) = true
+ * StringUtils.equalsAnyIgnoreCase(null, "abc", "def") = false
+ * StringUtils.equalsAnyIgnoreCase("abc", null, "def") = false
+ * StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
+ * StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true
+ * </pre>
+ *
+ * @param string to compare, may be {@code null}.
+ * @param searchStrings a vararg of strings, may be {@code null}.
+ * @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings};
+ * {@code false} if {@code searchStrings} is null or contains no matches.
+ * @since 3.5
+ */
+ public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence...searchStrings) {
+ if (ArrayUtils.isNotEmpty(searchStrings)) {
+ for (final CharSequence next : searchStrings) {
+ if (equalsIgnoreCase(string, next)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compares two CharSequences, returning {@code true} if they represent
+ * equal sequences of characters, ignoring case.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered equal. The comparison is <strong>case insensitive</strong>.</p>
+ *
+ * <pre>
+ * StringUtils.equalsIgnoreCase(null, null) = true
+ * StringUtils.equalsIgnoreCase(null, "abc") = false
+ * StringUtils.equalsIgnoreCase("abc", null) = false
+ * StringUtils.equalsIgnoreCase("abc", "abc") = true
+ * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+ * </pre>
+ *
+ * @param cs1 the first CharSequence, may be {@code null}
+ * @param cs2 the second CharSequence, may be {@code null}
+ * @return {@code true} if the CharSequences are equal (case-insensitive), or both {@code null}
+ * @since 3.0 Changed signature from equalsIgnoreCase(String, String) to equalsIgnoreCase(CharSequence, CharSequence)
+ * @see #equals(CharSequence, CharSequence)
+ */
+ public static boolean equalsIgnoreCase(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(cs1, true, 0, cs2, 0, cs1.length());
+ }
+
+ /**
+ * Returns the first value in the array which is not empty (""),
+ * {@code null} or whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>If all values are blank or the array is {@code null}
+ * or empty then {@code null} is returned.</p>
+ *
+ * <pre>
+ * StringUtils.firstNonBlank(null, null, null) = null
+ * StringUtils.firstNonBlank(null, "", " ") = null
+ * StringUtils.firstNonBlank("abc") = "abc"
+ * StringUtils.firstNonBlank(null, "xyz") = "xyz"
+ * StringUtils.firstNonBlank(null, "", " ", "xyz") = "xyz"
+ * StringUtils.firstNonBlank(null, "xyz", "abc") = "xyz"
+ * StringUtils.firstNonBlank() = null
+ * </pre>
+ *
+ * @param <T> the specific kind of CharSequence
+ * @param values the values to test, may be {@code null} or empty
+ * @return the first value from {@code values} which is not blank,
+ * or {@code null} if there are no non-blank values
+ * @since 3.8
+ */
+ @SafeVarargs
+ public static <T extends CharSequence> T firstNonBlank(final T... values) {
+ if (values != null) {
+ for (final T val : values) {
+ if (isNotBlank(val)) {
+ return val;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first value in the array which is not empty.
+ *
+ * <p>If all values are empty or the array is {@code null}
+ * or empty then {@code null} is returned.</p>
+ *
+ * <pre>
+ * StringUtils.firstNonEmpty(null, null, null) = null
+ * StringUtils.firstNonEmpty(null, null, "") = null
+ * StringUtils.firstNonEmpty(null, "", " ") = " "
+ * StringUtils.firstNonEmpty("abc") = "abc"
+ * StringUtils.firstNonEmpty(null, "xyz") = "xyz"
+ * StringUtils.firstNonEmpty("", "xyz") = "xyz"
+ * StringUtils.firstNonEmpty(null, "xyz", "abc") = "xyz"
+ * StringUtils.firstNonEmpty() = null
+ * </pre>
+ *
+ * @param <T> the specific kind of CharSequence
+ * @param values the values to test, may be {@code null} or empty
+ * @return the first value from {@code values} which is not empty,
+ * or {@code null} if there are no non-empty values
+ * @since 3.8
+ */
+ @SafeVarargs
+ public static <T extends CharSequence> T firstNonEmpty(final T... values) {
+ if (values != null) {
+ for (final T val : values) {
+ if (isNotEmpty(val)) {
+ return val;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Calls {@link String#getBytes(Charset)} in a null-safe manner.
+ *
+ * @param string input string
+ * @param charset The {@link Charset} to encode the {@link String}. If null, then use the default Charset.
+ * @return The empty byte[] if {@code string} is null, the result of {@link String#getBytes(Charset)} otherwise.
+ * @see String#getBytes(Charset)
+ * @since 3.10
+ */
+ public static byte[] getBytes(final String string, final Charset charset) {
+ return string == null ? ArrayUtils.EMPTY_BYTE_ARRAY : string.getBytes(Charsets.toCharset(charset));
+ }
+
+ /**
+ * Calls {@link String#getBytes(String)} in a null-safe manner.
+ *
+ * @param string input string
+ * @param charset The {@link Charset} name to encode the {@link String}. If null, then use the default Charset.
+ * @return The empty byte[] if {@code string} is null, the result of {@link String#getBytes(String)} otherwise.
+ * @throws UnsupportedEncodingException Thrown when the named charset is not supported.
+ * @see String#getBytes(String)
+ * @since 3.10
+ */
+ public static byte[] getBytes(final String string, final String charset) throws UnsupportedEncodingException {
+ return string == null ? ArrayUtils.EMPTY_BYTE_ARRAY : string.getBytes(Charsets.toCharsetName(charset));
+ }
+
+ /**
+ * Compares all Strings in an array and returns the initial sequence of
+ * characters that is common to all of them.
+ *
+ * <p>For example,
+ * {@code getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) -&gt; "i am a "}</p>
+ *
+ * <pre>
+ * StringUtils.getCommonPrefix(null) = ""
+ * StringUtils.getCommonPrefix(new String[] {}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", null, null}) = ""
+ * StringUtils.getCommonPrefix(new String[] {null, null, "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"", "abc"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", ""}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"abc", "abc"}) = "abc"
+ * StringUtils.getCommonPrefix(new String[] {"abc", "a"}) = "a"
+ * StringUtils.getCommonPrefix(new String[] {"ab", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "abxyz"}) = "ab"
+ * StringUtils.getCommonPrefix(new String[] {"abcde", "xyz"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"xyz", "abcde"}) = ""
+ * StringUtils.getCommonPrefix(new String[] {"i am a machine", "i am a robot"}) = "i am a "
+ * </pre>
+ *
+ * @param strs array of String objects, entries may be null
+ * @return the initial sequence of characters that are common to all Strings
+ * in the array; empty String if the array is null, the elements are all null
+ * or if there is no common prefix.
+ * @since 2.4
+ */
+ public static String getCommonPrefix(final String... strs) {
+ if (ArrayUtils.isEmpty(strs)) {
+ return EMPTY;
+ }
+ final int smallestIndexOfDiff = indexOfDifference(strs);
+ if (smallestIndexOfDiff == INDEX_NOT_FOUND) {
+ // all strings were identical
+ if (strs[0] == null) {
+ return EMPTY;
+ }
+ return strs[0];
+ }
+ if (smallestIndexOfDiff == 0) {
+ // there were no common initial characters
+ return EMPTY;
+ }
+ // we found a common initial character sequence
+ return strs[0].substring(0, smallestIndexOfDiff);
+ }
+
+ /**
+ * Checks if a String {@code str} contains Unicode digits,
+ * if yes then concatenate all the digits in {@code str} and return it as a String.
+ *
+ * <p>An empty ("") String will be returned if no digits found in {@code str}.</p>
+ *
+ * <pre>
+ * StringUtils.getDigits(null) = null
+ * StringUtils.getDigits("") = ""
+ * StringUtils.getDigits("abc") = ""
+ * StringUtils.getDigits("1000$") = "1000"
+ * StringUtils.getDigits("1123~45") = "112345"
+ * StringUtils.getDigits("(541) 754-3010") = "5417543010"
+ * StringUtils.getDigits("\u0967\u0968\u0969") = "\u0967\u0968\u0969"
+ * </pre>
+ *
+ * @param str the String to extract digits from, may be null
+ * @return String with only digits,
+ * or an empty ("") String if no digits found,
+ * or {@code null} String if {@code str} is null
+ * @since 3.6
+ */
+ public static String getDigits(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int sz = str.length();
+ final StringBuilder strDigits = new StringBuilder(sz);
+ for (int i = 0; i < sz; i++) {
+ final char tempChar = str.charAt(i);
+ if (Character.isDigit(tempChar)) {
+ strDigits.append(tempChar);
+ }
+ }
+ return strDigits.toString();
+ }
+
+ /**
+ * Find the Fuzzy Distance which indicates the similarity score between two Strings.
+ *
+ * <p>This string matching algorithm is similar to the algorithms of editors such as Sublime Text,
+ * TextMate, Atom and others. One point is given for every matched character. Subsequent
+ * matches yield two bonus points. A higher score indicates a higher similarity.</p>
+ *
+ * <pre>
+ * StringUtils.getFuzzyDistance(null, null, null) = IllegalArgumentException
+ * StringUtils.getFuzzyDistance("", "", Locale.ENGLISH) = 0
+ * StringUtils.getFuzzyDistance("Workshop", "b", Locale.ENGLISH) = 0
+ * StringUtils.getFuzzyDistance("Room", "o", Locale.ENGLISH) = 1
+ * StringUtils.getFuzzyDistance("Workshop", "w", Locale.ENGLISH) = 1
+ * StringUtils.getFuzzyDistance("Workshop", "ws", Locale.ENGLISH) = 2
+ * StringUtils.getFuzzyDistance("Workshop", "wo", Locale.ENGLISH) = 4
+ * StringUtils.getFuzzyDistance("Apache Software Foundation", "asf", Locale.ENGLISH) = 3
+ * </pre>
+ *
+ * @param term a full term that should be matched against, must not be null
+ * @param query the query that will be matched against a term, must not be null
+ * @param locale This string matching logic is case-insensitive. A locale is necessary to normalize
+ * both Strings to lower case.
+ * @return result score
+ * @throws IllegalArgumentException if either String input {@code null} or Locale input {@code null}
+ * @since 3.4
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/similarity/FuzzyScore.html">
+ * FuzzyScore</a> instead
+ */
+ @Deprecated
+ public static int getFuzzyDistance(final CharSequence term, final CharSequence query, final Locale locale) {
+ if (term == null || query == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+ if (locale == null) {
+ throw new IllegalArgumentException("Locale must not be null");
+ }
+
+ // fuzzy logic is case-insensitive. We normalize the Strings to lower
+ // case right from the start. Turning characters to lower case
+ // via Character.toLowerCase(char) is unfortunately insufficient
+ // as it does not accept a locale.
+ final String termLowerCase = term.toString().toLowerCase(locale);
+ final String queryLowerCase = query.toString().toLowerCase(locale);
+
+ // the resulting score
+ int score = 0;
+
+ // the position in the term which will be scanned next for potential
+ // query character matches
+ int termIndex = 0;
+
+ // index of the previously matched character in the term
+ int previousMatchingCharacterIndex = Integer.MIN_VALUE;
+
+ for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) {
+ final char queryChar = queryLowerCase.charAt(queryIndex);
+
+ boolean termCharacterMatchFound = false;
+ for (; termIndex < termLowerCase.length() && !termCharacterMatchFound; termIndex++) {
+ final char termChar = termLowerCase.charAt(termIndex);
+
+ if (queryChar == termChar) {
+ // simple character matches result in one point
+ score++;
+
+ // subsequent character matches further improve
+ // the score.
+ if (previousMatchingCharacterIndex + 1 == termIndex) {
+ score += 2;
+ }
+
+ previousMatchingCharacterIndex = termIndex;
+
+ // we can leave the nested loop. Every character in the
+ // query can match at most one character in the term.
+ termCharacterMatchFound = true;
+ }
+ }
+ }
+
+ return score;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * whitespace, empty ("") or {@code null}, the value supplied by {@code defaultStrSupplier}.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>Caller responsible for thread-safety and exception handling of default value supplier</p>
+ *
+ * <pre>
+ * {@code
+ * StringUtils.getIfBlank(null, () -> "NULL") = "NULL"
+ * StringUtils.getIfBlank("", () -> "NULL") = "NULL"
+ * StringUtils.getIfBlank(" ", () -> "NULL") = "NULL"
+ * StringUtils.getIfBlank("bat", () -> "NULL") = "bat"
+ * StringUtils.getIfBlank("", () -> null) = null
+ * StringUtils.getIfBlank("", null) = null
+ * }</pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultSupplier the supplier of default CharSequence to return
+ * if the input is whitespace, empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ * @since 3.10
+ */
+ public static <T extends CharSequence> T getIfBlank(final T str, final Supplier<T> defaultSupplier) {
+ return isBlank(str) ? Suppliers.get(defaultSupplier) : str;
+ }
+
+ /**
+ * Returns either the passed in CharSequence, or if the CharSequence is
+ * empty or {@code null}, the value supplied by {@code defaultStrSupplier}.
+ *
+ * <p>Caller responsible for thread-safety and exception handling of default value supplier</p>
+ *
+ * <pre>
+ * {@code
+ * StringUtils.getIfEmpty(null, () -> "NULL") = "NULL"
+ * StringUtils.getIfEmpty("", () -> "NULL") = "NULL"
+ * StringUtils.getIfEmpty(" ", () -> "NULL") = " "
+ * StringUtils.getIfEmpty("bat", () -> "NULL") = "bat"
+ * StringUtils.getIfEmpty("", () -> null) = null
+ * StringUtils.getIfEmpty("", null) = null
+ * }
+ * </pre>
+ * @param <T> the specific kind of CharSequence
+ * @param str the CharSequence to check, may be null
+ * @param defaultSupplier the supplier of default CharSequence to return
+ * if the input is empty ("") or {@code null}, may be null
+ * @return the passed in CharSequence, or the default
+ * @see StringUtils#defaultString(String, String)
+ * @since 3.10
+ */
+ public static <T extends CharSequence> T getIfEmpty(final T str, final Supplier<T> defaultSupplier) {
+ return isEmpty(str) ? Suppliers.get(defaultSupplier) : str;
+ }
+
+ /**
+ * Find the Jaro Winkler Distance which indicates the similarity score between two Strings.
+ *
+ * <p>The Jaro measure is the weighted sum of percentage of matched characters from each file and transposed characters.
+ * Winkler increased this measure for matching initial characters.</p>
+ *
+ * <p>This implementation is based on the Jaro Winkler similarity algorithm
+ * from <a href="https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance">https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance</a>.</p>
+ *
+ * <pre>
+ * StringUtils.getJaroWinklerDistance(null, null) = IllegalArgumentException
+ * StringUtils.getJaroWinklerDistance("", "") = 0.0
+ * StringUtils.getJaroWinklerDistance("", "a") = 0.0
+ * StringUtils.getJaroWinklerDistance("aaapppp", "") = 0.0
+ * StringUtils.getJaroWinklerDistance("frog", "fog") = 0.93
+ * StringUtils.getJaroWinklerDistance("fly", "ant") = 0.0
+ * StringUtils.getJaroWinklerDistance("elephant", "hippo") = 0.44
+ * StringUtils.getJaroWinklerDistance("hippo", "elephant") = 0.44
+ * StringUtils.getJaroWinklerDistance("hippo", "zzzzzzzz") = 0.0
+ * StringUtils.getJaroWinklerDistance("hello", "hallo") = 0.88
+ * StringUtils.getJaroWinklerDistance("ABC Corporation", "ABC Corp") = 0.93
+ * StringUtils.getJaroWinklerDistance("D N H Enterprises Inc", "D &amp; H Enterprises, Inc.") = 0.95
+ * StringUtils.getJaroWinklerDistance("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.92
+ * StringUtils.getJaroWinklerDistance("PENNSYLVANIA", "PENNCISYLVNIA") = 0.88
+ * </pre>
+ *
+ * @param first the first String, must not be null
+ * @param second the second String, must not be null
+ * @return result distance
+ * @throws IllegalArgumentException if either String input {@code null}
+ * @since 3.3
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/similarity/JaroWinklerDistance.html">
+ * JaroWinklerDistance</a> instead
+ */
+ @Deprecated
+ public static double getJaroWinklerDistance(final CharSequence first, final CharSequence second) {
+ final double DEFAULT_SCALING_FACTOR = 0.1;
+
+ if (first == null || second == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+
+ final int[] mtp = matches(first, second);
+ final double m = mtp[0];
+ if (m == 0) {
+ return 0D;
+ }
+ final double j = (m / first.length() + m / second.length() + (m - mtp[1]) / m) / 3;
+ final double jw = j < 0.7D ? j : j + Math.min(DEFAULT_SCALING_FACTOR, 1D / mtp[3]) * mtp[2] * (1D - j);
+ return Math.round(jw * 100.0D) / 100.0D;
+ }
+
+ /**
+ * Find the Levenshtein distance between two Strings.
+ *
+ * <p>This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution).</p>
+ *
+ * <p>The implementation uses a single-dimensional array of length s.length() + 1. See
+ * <a href="https://blog.softwx.net/2014/12/optimizing-levenshtein-algorithm-in-c.html">
+ * https://blog.softwx.net/2014/12/optimizing-levenshtein-algorithm-in-c.html</a> for details.</p>
+ *
+ * <pre>
+ * StringUtils.getLevenshteinDistance(null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("", "") = 0
+ * StringUtils.getLevenshteinDistance("", "a") = 1
+ * StringUtils.getLevenshteinDistance("aaapppp", "") = 7
+ * StringUtils.getLevenshteinDistance("frog", "fog") = 1
+ * StringUtils.getLevenshteinDistance("fly", "ant") = 3
+ * StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
+ * StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+ * StringUtils.getLevenshteinDistance("hello", "hallo") = 1
+ * </pre>
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @return result distance
+ * @throws IllegalArgumentException if either String input {@code null}
+ * @since 3.0 Changed signature from getLevenshteinDistance(String, String) to
+ * getLevenshteinDistance(CharSequence, CharSequence)
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/similarity/LevenshteinDistance.html">
+ * LevenshteinDistance</a> instead
+ */
+ @Deprecated
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+
+ int n = s.length();
+ int m = t.length();
+
+ if (n == 0) {
+ return m;
+ }
+ if (m == 0) {
+ return n;
+ }
+
+ if (n > m) {
+ // swap the input strings to consume less memory
+ final CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ final int[] p = new int[n + 1];
+ // indexes into strings s and t
+ int i; // iterates through s
+ int j; // iterates through t
+ int upper_left;
+ int upper;
+
+ char t_j; // jth character of t
+ int cost;
+
+ for (i = 0; i <= n; i++) {
+ p[i] = i;
+ }
+
+ for (j = 1; j <= m; j++) {
+ upper_left = p[0];
+ t_j = t.charAt(j - 1);
+ p[0] = j;
+
+ for (i = 1; i <= n; i++) {
+ upper = p[i];
+ cost = s.charAt(i - 1) == t_j ? 0 : 1;
+ // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
+ p[i] = Math.min(Math.min(p[i - 1] + 1, p[i] + 1), upper_left + cost);
+ upper_left = upper;
+ }
+ }
+
+ return p[n];
+ }
+
+ /**
+ * Find the Levenshtein distance between two Strings if it's less than or equal to a given
+ * threshold.
+ *
+ * <p>This is the number of changes needed to change one String into
+ * another, where each change is a single character modification (deletion,
+ * insertion or substitution).</p>
+ *
+ * <p>This implementation follows from Algorithms on Strings, Trees and Sequences by Dan Gusfield
+ * and Chas Emerick's implementation of the Levenshtein distance algorithm from
+ * <a href="https://web.archive.org/web/20120212021906/http%3A//www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</a></p>
+ *
+ * <pre>
+ * StringUtils.getLevenshteinDistance(null, *, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, null, *) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance(*, *, -1) = IllegalArgumentException
+ * StringUtils.getLevenshteinDistance("", "", 0) = 0
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 8) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 7) = 7
+ * StringUtils.getLevenshteinDistance("aaapppp", "", 6)) = -1
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 7) = 7
+ * StringUtils.getLevenshteinDistance("elephant", "hippo", 6) = -1
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 7) = 7
+ * StringUtils.getLevenshteinDistance("hippo", "elephant", 6) = -1
+ * </pre>
+ *
+ * @param s the first String, must not be null
+ * @param t the second String, must not be null
+ * @param threshold the target threshold, must not be negative
+ * @return result distance, or {@code -1} if the distance would be greater than the threshold
+ * @throws IllegalArgumentException if either String input {@code null} or negative threshold
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/similarity/LevenshteinDistance.html">
+ * LevenshteinDistance</a> instead
+ */
+ @Deprecated
+ public static int getLevenshteinDistance(CharSequence s, CharSequence t, final int threshold) {
+ if (s == null || t == null) {
+ throw new IllegalArgumentException("Strings must not be null");
+ }
+ if (threshold < 0) {
+ throw new IllegalArgumentException("Threshold must not be negative");
+ }
+
+ /*
+ This implementation only computes the distance if it's less than or equal to the
+ threshold value, returning -1 if it's greater. The advantage is performance: unbounded
+ distance is O(nm), but a bound of k allows us to reduce it to O(km) time by only
+ computing a diagonal stripe of width 2k + 1 of the cost table.
+ It is also possible to use this to compute the unbounded Levenshtein distance by starting
+ the threshold at 1 and doubling each time until the distance is found; this is O(dm), where
+ d is the distance.
+
+ One subtlety comes from needing to ignore entries on the border of our stripe
+ eg.
+ p[] = |#|#|#|*
+ d[] = *|#|#|#|
+ We must ignore the entry to the left of the leftmost member
+ We must ignore the entry above the rightmost member
+
+ Another subtlety comes from our stripe running off the matrix if the strings aren't
+ of the same size. Since string s is always swapped to be the shorter of the two,
+ the stripe will always run off to the upper right instead of the lower left of the matrix.
+
+ As a concrete example, suppose s is of length 5, t is of length 7, and our threshold is 1.
+ In this case we're going to walk a stripe of length 3. The matrix would look like so:
+
+ 1 2 3 4 5
+ 1 |#|#| | | |
+ 2 |#|#|#| | |
+ 3 | |#|#|#| |
+ 4 | | |#|#|#|
+ 5 | | | |#|#|
+ 6 | | | | |#|
+ 7 | | | | | |
+
+ Note how the stripe leads off the table as there is no possible way to turn a string of length 5
+ into one of length 7 in edit distance of 1.
+
+ Additionally, this implementation decreases memory usage by using two
+ single-dimensional arrays and swapping them back and forth instead of allocating
+ an entire n by m matrix. This requires a few minor changes, such as immediately returning
+ when it's detected that the stripe has run off the matrix and initially filling the arrays with
+ large values so that entries we don't compute are ignored.
+
+ See Algorithms on Strings, Trees and Sequences by Dan Gusfield for some discussion.
+ */
+
+ int n = s.length(); // length of s
+ int m = t.length(); // length of t
+
+ // if one string is empty, the edit distance is necessarily the length of the other
+ if (n == 0) {
+ return m <= threshold ? m : -1;
+ }
+ if (m == 0) {
+ return n <= threshold ? n : -1;
+ }
+ if (Math.abs(n - m) > threshold) {
+ // no need to calculate the distance if the length difference is greater than the threshold
+ return -1;
+ }
+
+ if (n > m) {
+ // swap the two strings to consume less memory
+ final CharSequence tmp = s;
+ s = t;
+ t = tmp;
+ n = m;
+ m = t.length();
+ }
+
+ int[] p = new int[n + 1]; // 'previous' cost array, horizontally
+ int[] d = new int[n + 1]; // cost array, horizontally
+ int[] tmp; // placeholder to assist in swapping p and d
+
+ // fill in starting table values
+ final int boundary = Math.min(n, threshold) + 1;
+ for (int i = 0; i < boundary; i++) {
+ p[i] = i;
+ }
+ // these fills ensure that the value above the rightmost entry of our
+ // stripe will be ignored in following loop iterations
+ Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE);
+ Arrays.fill(d, Integer.MAX_VALUE);
+
+ // iterates through t
+ for (int j = 1; j <= m; j++) {
+ final char t_j = t.charAt(j - 1); // jth character of t
+ d[0] = j;
+
+ // compute stripe indices, constrain to array size
+ final int min = Math.max(1, j - threshold);
+ final int max = j > Integer.MAX_VALUE - threshold ? n : Math.min(n, j + threshold);
+
+ // the stripe may lead off of the table if s and t are of different sizes
+ if (min > max) {
+ return -1;
+ }
+
+ // ignore entry left of leftmost
+ if (min > 1) {
+ d[min - 1] = Integer.MAX_VALUE;
+ }
+
+ // iterates through [min, max] in s
+ for (int i = min; i <= max; i++) {
+ if (s.charAt(i - 1) == t_j) {
+ // diagonally left and up
+ d[i] = p[i - 1];
+ } else {
+ // 1 + minimum of cell to the left, to the top, diagonally left and up
+ d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);
+ }
+ }
+
+ // copy current distance counts to 'previous row' distance counts
+ tmp = p;
+ p = d;
+ d = tmp;
+ }
+
+ // if p[n] is greater than the threshold, there's no guarantee on it being the correct
+ // distance
+ if (p[n] <= threshold) {
+ return p[n];
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String, int)} if possible.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf(*, null) = -1
+ * StringUtils.indexOf("", "") = 0
+ * StringUtils.indexOf("", *) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a") = 0
+ * StringUtils.indexOf("aabaabaa", "b") = 2
+ * StringUtils.indexOf("aabaabaa", "ab") = 1
+ * StringUtils.indexOf("aabaabaa", "") = 0
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence)
+ */
+ public static int indexOf(final CharSequence seq, final CharSequence searchSeq) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, 0);
+ }
+
+ /**
+ * Finds the first index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String, int)} if possible.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf(*, null, *) = -1
+ * StringUtils.indexOf("", "", 0) = 0
+ * StringUtils.indexOf("", *, 0) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.indexOf("aabaabaa", "b", 0) = 2
+ * StringUtils.indexOf("aabaabaa", "ab", 0) = 1
+ * StringUtils.indexOf("aabaabaa", "b", 3) = 5
+ * StringUtils.indexOf("aabaabaa", "b", 9) = -1
+ * StringUtils.indexOf("aabaabaa", "b", -1) = 2
+ * StringUtils.indexOf("aabaabaa", "", 2) = 2
+ * StringUtils.indexOf("abc", "", 9) = 3
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search CharSequence (always &ge; startPos),
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, String, int) to indexOf(CharSequence, CharSequence, int)
+ */
+ public static int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) {
+ if (seq == null || searchSeq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchSeq, startPos);
+ }
+
+ /**
+ * Returns the index within {@code seq} of the first occurrence of
+ * the specified character. If a character with value
+ * {@code searchChar} occurs in the character sequence represented by
+ * {@code seq} {@link CharSequence} object, then the index (in Unicode
+ * code units) of the first such occurrence is returned. For
+ * values of {@code searchChar} in the range from 0 to 0xFFFF
+ * (inclusive), this is the smallest value <i>k</i> such that:
+ * <blockquote><pre>
+ * this.charAt(<i>k</i>) == searchChar
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * smallest value <i>k</i> such that:
+ * <blockquote><pre>
+ * this.codePointAt(<i>k</i>) == searchChar
+ * </pre></blockquote>
+ * is true. In either case, if no such character occurs in {@code seq},
+ * then {@code INDEX_NOT_FOUND (-1)} is returned.
+ *
+ * <p>Furthermore, a {@code null} or empty ("") CharSequence will
+ * return {@code INDEX_NOT_FOUND (-1)}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf("", *) = -1
+ * StringUtils.indexOf("aabaabaa", 'a') = 0
+ * StringUtils.indexOf("aabaabaa", 'b') = 2
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @return the first index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, int) to indexOf(CharSequence, int)
+ * @since 3.6 Updated {@link CharSequenceUtils} call to behave more like {@link String}
+ */
+ public static int indexOf(final CharSequence seq, final int searchChar) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, 0);
+ }
+
+ /**
+ * Returns the index within {@code seq} of the first occurrence of the
+ * specified character, starting the search at the specified index.
+ * <p>
+ * If a character with value {@code searchChar} occurs in the
+ * character sequence represented by the {@code seq} {@link CharSequence}
+ * object at an index no smaller than {@code startPos}, then
+ * the index of the first such occurrence is returned. For values
+ * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
+ * this is the smallest value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= startPos)
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * smallest value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= startPos)
+ * </pre></blockquote>
+ * is true. In either case, if no such character occurs in {@code seq}
+ * at or after position {@code startPos}, then
+ * {@code -1} is returned.
+ *
+ * <p>
+ * There is no restriction on the value of {@code startPos}. If it
+ * is negative, it has the same effect as if it were zero: this entire
+ * string may be searched. If it is greater than the length of this
+ * string, it has the same effect as if it were equal to the length of
+ * this string: {@code (INDEX_NOT_FOUND) -1} is returned. Furthermore, a
+ * {@code null} or empty ("") CharSequence will
+ * return {@code (INDEX_NOT_FOUND) -1}.
+ *
+ * <p>All indices are specified in {@code char} values
+ * (Unicode code units).
+ *
+ * <pre>
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf("", *, *) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', 0) = 2
+ * StringUtils.indexOf("aabaabaa", 'b', 3) = 5
+ * StringUtils.indexOf("aabaabaa", 'b', 9) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', -1) = 2
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search character (always &ge; startPos),
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOf(String, int, int) to indexOf(CharSequence, int, int)
+ * @since 3.6 Updated {@link CharSequenceUtils} call to behave more like {@link String}
+ */
+ public static int indexOf(final CharSequence seq, final int searchChar, final int startPos) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.indexOf(seq, searchChar, startPos);
+ }
+
+ /**
+ * Search a CharSequence to find the first index of any
+ * character in the given set of characters.
+ *
+ * <p>A {@code null} String will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny("", *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, []) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ['z', 'a']) = 0
+ * StringUtils.indexOfAny("zzabyycdxx", ['b', 'y']) = 3
+ * StringUtils.indexOfAny("aba", ['z']) = -1
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...)
+ */
+ public static int indexOfAny(final CharSequence cs, final char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ final int csLen = cs.length();
+ final int csLast = csLen - 1;
+ final int searchLen = searchChars.length;
+ final int searchLast = searchLen - 1;
+ for (int i = 0; i < csLen; i++) {
+ final char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (i >= csLast || j >= searchLast || !Character.isHighSurrogate(ch)) {
+ return i;
+ }
+ // ch is a supplementary character
+ if (searchChars[j + 1] == cs.charAt(i + 1)) {
+ return i;
+ }
+ }
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Find the first index of any of a set of potential substrings.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.
+ * A {@code null} search array entry will be ignored, but a search
+ * array containing "" will return {@code 0} if {@code str} is not
+ * null. This method uses {@link String#indexOf(String)} if possible.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, []) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["ab", "cd"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["cd", "ab"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["mn", "op"]) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["zab", "aby"]) = 1
+ * StringUtils.indexOfAny("zzabyycdxx", [""]) = 0
+ * StringUtils.indexOfAny("", [""]) = 0
+ * StringUtils.indexOfAny("", ["a"]) = -1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStrs the CharSequences to search for, may be null
+ * @return the first index of any of the searchStrs in str, -1 if no match
+ * @since 3.0 Changed signature from indexOfAny(String, String[]) to indexOfAny(CharSequence, CharSequence...)
+ */
+ public static int indexOfAny(final CharSequence str, final CharSequence... searchStrs) {
+ if (str == null || searchStrs == null) {
+ return INDEX_NOT_FOUND;
+ }
+
+ // String's can't have a MAX_VALUEth index.
+ int ret = Integer.MAX_VALUE;
+
+ int tmp;
+ for (final CharSequence search : searchStrs) {
+ if (search == null) {
+ continue;
+ }
+ tmp = CharSequenceUtils.indexOf(str, search, 0);
+ if (tmp == INDEX_NOT_FOUND) {
+ continue;
+ }
+
+ if (tmp < ret) {
+ ret = tmp;
+ }
+ }
+
+ return ret == Integer.MAX_VALUE ? INDEX_NOT_FOUND : ret;
+ }
+
+ /**
+ * Search a CharSequence to find the first index of any
+ * character in the given set of characters.
+ *
+ * <p>A {@code null} String will return {@code -1}.
+ * A {@code null} search string will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny("", *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, "") = -1
+ * StringUtils.indexOfAny("zzabyycdxx", "za") = 0
+ * StringUtils.indexOfAny("zzabyycdxx", "by") = 3
+ * StringUtils.indexOfAny("aba", "z") = -1
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, String)
+ */
+ public static int indexOfAny(final CharSequence cs, final String searchChars) {
+ if (isEmpty(cs) || isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ return indexOfAny(cs, searchChars.toCharArray());
+ }
+
+ /**
+ * Searches a CharSequence to find the first index of any
+ * character not in the given set of characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or zero length search array will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAnyBut(null, *) = -1
+ * StringUtils.indexOfAnyBut("", *) = -1
+ * StringUtils.indexOfAnyBut(*, null) = -1
+ * StringUtils.indexOfAnyBut(*, []) = -1
+ * StringUtils.indexOfAnyBut("zzabyycdxx", new char[] {'z', 'a'} ) = 3
+ * StringUtils.indexOfAnyBut("aba", new char[] {'z'} ) = 0
+ * StringUtils.indexOfAnyBut("aba", new char[] {'a', 'b'} ) = -1
+
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAnyBut(String, char[]) to indexOfAnyBut(CharSequence, char...)
+ */
+ public static int indexOfAnyBut(final CharSequence cs, final char... searchChars) {
+ if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ final int csLen = cs.length();
+ final int csLast = csLen - 1;
+ final int searchLen = searchChars.length;
+ final int searchLast = searchLen - 1;
+ outer:
+ for (int i = 0; i < csLen; i++) {
+ final char ch = cs.charAt(i);
+ for (int j = 0; j < searchLen; j++) {
+ if (searchChars[j] == ch) {
+ if (i >= csLast || j >= searchLast || !Character.isHighSurrogate(ch)) {
+ continue outer;
+ }
+ if (searchChars[j + 1] == cs.charAt(i + 1)) {
+ continue outer;
+ }
+ }
+ }
+ return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Search a CharSequence to find the first index of any
+ * character not in the given set of characters.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} or empty search string will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfAnyBut(null, *) = -1
+ * StringUtils.indexOfAnyBut("", *) = -1
+ * StringUtils.indexOfAnyBut(*, null) = -1
+ * StringUtils.indexOfAnyBut(*, "") = -1
+ * StringUtils.indexOfAnyBut("zzabyycdxx", "za") = 3
+ * StringUtils.indexOfAnyBut("zzabyycdxx", "") = -1
+ * StringUtils.indexOfAnyBut("aba", "ab") = -1
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChars the chars to search for, may be null
+ * @return the index of any of the chars, -1 if no match or null input
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfAnyBut(String, String) to indexOfAnyBut(CharSequence, CharSequence)
+ */
+ public static int indexOfAnyBut(final CharSequence seq, final CharSequence searchChars) {
+ if (isEmpty(seq) || isEmpty(searchChars)) {
+ return INDEX_NOT_FOUND;
+ }
+ final int strLen = seq.length();
+ for (int i = 0; i < strLen; i++) {
+ final char ch = seq.charAt(i);
+ final boolean chFound = CharSequenceUtils.indexOf(searchChars, ch, 0) >= 0;
+ if (i + 1 < strLen && Character.isHighSurrogate(ch)) {
+ final char ch2 = seq.charAt(i + 1);
+ if (chFound && CharSequenceUtils.indexOf(searchChars, ch2, 0) < 0) {
+ return i;
+ }
+ } else if (!chFound) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Compares all CharSequences in an array and returns the index at which the
+ * CharSequences begin to differ.
+ *
+ * <p>For example,
+ * {@code indexOfDifference(new String[] {"i am a machine", "i am a robot"}) -> 7}</p>
+ *
+ * <pre>
+ * StringUtils.indexOfDifference(null) = -1
+ * StringUtils.indexOfDifference(new String[] {}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {null, null}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", ""}) = -1
+ * StringUtils.indexOfDifference(new String[] {"", null}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", null, null}) = 0
+ * StringUtils.indexOfDifference(new String[] {null, null, "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"", "abc"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", ""}) = 0
+ * StringUtils.indexOfDifference(new String[] {"abc", "abc"}) = -1
+ * StringUtils.indexOfDifference(new String[] {"abc", "a"}) = 1
+ * StringUtils.indexOfDifference(new String[] {"ab", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "abxyz"}) = 2
+ * StringUtils.indexOfDifference(new String[] {"abcde", "xyz"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"xyz", "abcde"}) = 0
+ * StringUtils.indexOfDifference(new String[] {"i am a machine", "i am a robot"}) = 7
+ * </pre>
+ *
+ * @param css array of CharSequences, entries may be null
+ * @return the index where the strings begin to differ; -1 if they are all equal
+ * @since 2.4
+ * @since 3.0 Changed signature from indexOfDifference(String...) to indexOfDifference(CharSequence...)
+ */
+ public static int indexOfDifference(final CharSequence... css) {
+ if (ArrayUtils.getLength(css) <= 1) {
+ return INDEX_NOT_FOUND;
+ }
+ boolean anyStringNull = false;
+ boolean allStringsNull = true;
+ final int arrayLen = css.length;
+ int shortestStrLen = Integer.MAX_VALUE;
+ int longestStrLen = 0;
+
+ // find the min and max string lengths; this avoids checking to make
+ // sure we are not exceeding the length of the string each time through
+ // the bottom loop.
+ for (final CharSequence cs : css) {
+ if (cs == null) {
+ anyStringNull = true;
+ shortestStrLen = 0;
+ } else {
+ allStringsNull = false;
+ shortestStrLen = Math.min(cs.length(), shortestStrLen);
+ longestStrLen = Math.max(cs.length(), longestStrLen);
+ }
+ }
+
+ // handle lists containing all nulls or all empty strings
+ if (allStringsNull || longestStrLen == 0 && !anyStringNull) {
+ return INDEX_NOT_FOUND;
+ }
+
+ // handle lists containing some nulls or some empty strings
+ if (shortestStrLen == 0) {
+ return 0;
+ }
+
+ // find the position with the first difference across all strings
+ int firstDiff = -1;
+ for (int stringPos = 0; stringPos < shortestStrLen; stringPos++) {
+ final char comparisonChar = css[0].charAt(stringPos);
+ for (int arrayPos = 1; arrayPos < arrayLen; arrayPos++) {
+ if (css[arrayPos].charAt(stringPos) != comparisonChar) {
+ firstDiff = stringPos;
+ break;
+ }
+ }
+ if (firstDiff != -1) {
+ break;
+ }
+ }
+
+ if (firstDiff == -1 && shortestStrLen != longestStrLen) {
+ // we compared all of the characters up to the length of the
+ // shortest string and didn't find a match, but the string lengths
+ // vary, so return the length of the shortest string.
+ return shortestStrLen;
+ }
+ return firstDiff;
+ }
+
+ /**
+ * Compares two CharSequences, and returns the index at which the
+ * CharSequences begin to differ.
+ *
+ * <p>For example,
+ * {@code indexOfDifference("i am a machine", "i am a robot") -> 7}</p>
+ *
+ * <pre>
+ * StringUtils.indexOfDifference(null, null) = -1
+ * StringUtils.indexOfDifference("", "") = -1
+ * StringUtils.indexOfDifference("", "abc") = 0
+ * StringUtils.indexOfDifference("abc", "") = 0
+ * StringUtils.indexOfDifference("abc", "abc") = -1
+ * StringUtils.indexOfDifference("ab", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "abxyz") = 2
+ * StringUtils.indexOfDifference("abcde", "xyz") = 0
+ * </pre>
+ *
+ * @param cs1 the first CharSequence, may be null
+ * @param cs2 the second CharSequence, may be null
+ * @return the index where cs1 and cs2 begin to differ; -1 if they are equal
+ * @since 2.0
+ * @since 3.0 Changed signature from indexOfDifference(String, String) to
+ * indexOfDifference(CharSequence, CharSequence)
+ */
+ public static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return INDEX_NOT_FOUND;
+ }
+ if (cs1 == null || cs2 == null) {
+ return 0;
+ }
+ int i;
+ for (i = 0; i < cs1.length() && i < cs2.length(); ++i) {
+ if (cs1.charAt(i) != cs2.charAt(i)) {
+ break;
+ }
+ }
+ if (i < cs2.length() || i < cs1.length()) {
+ return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Case in-sensitive find of the first index within a CharSequence.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfIgnoreCase(null, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null) = -1
+ * StringUtils.indexOfIgnoreCase("", "") = 0
+ * StringUtils.indexOfIgnoreCase(" ", " ") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "a") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "b") = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "ab") = 1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from indexOfIgnoreCase(String, String) to indexOfIgnoreCase(CharSequence, CharSequence)
+ */
+ public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ return indexOfIgnoreCase(str, searchStr, 0);
+ }
+
+ /**
+ * Case in-sensitive find of the first index within a CharSequence
+ * from the specified position.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position is treated as zero.
+ * An empty ("") search CharSequence always matches.
+ * A start position greater than the string length only matches
+ * an empty search CharSequence.</p>
+ *
+ * <pre>
+ * StringUtils.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtils.indexOfIgnoreCase("abc", "", 9) = -1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search CharSequence (always &ge; startPos),
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from indexOfIgnoreCase(String, String, int) to indexOfIgnoreCase(CharSequence, CharSequence, int)
+ */
+ public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startPos < 0) {
+ startPos = 0;
+ }
+ final int endLimit = str.length() - searchStr.length() + 1;
+ if (startPos > endLimit) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return startPos;
+ }
+ for (int i = startPos; i < endLimit; i++) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Checks if all of the CharSequences are empty (""), null or whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.isAllBlank(null) = true
+ * StringUtils.isAllBlank(null, "foo") = false
+ * StringUtils.isAllBlank(null, null) = true
+ * StringUtils.isAllBlank("", "bar") = false
+ * StringUtils.isAllBlank("bob", "") = false
+ * StringUtils.isAllBlank(" bob ", null) = false
+ * StringUtils.isAllBlank(" ", "bar") = false
+ * StringUtils.isAllBlank("foo", "bar") = false
+ * StringUtils.isAllBlank(new String[] {}) = true
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if all of the CharSequences are empty or null or whitespace only
+ * @since 3.6
+ */
+ public static boolean isAllBlank(final CharSequence... css) {
+ if (ArrayUtils.isEmpty(css)) {
+ return true;
+ }
+ for (final CharSequence cs : css) {
+ if (isNotBlank(cs)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if all of the CharSequences are empty ("") or null.
+ *
+ * <pre>
+ * StringUtils.isAllEmpty(null) = true
+ * StringUtils.isAllEmpty(null, "") = true
+ * StringUtils.isAllEmpty(new String[] {}) = true
+ * StringUtils.isAllEmpty(null, "foo") = false
+ * StringUtils.isAllEmpty("", "bar") = false
+ * StringUtils.isAllEmpty("bob", "") = false
+ * StringUtils.isAllEmpty(" bob ", null) = false
+ * StringUtils.isAllEmpty(" ", "bar") = false
+ * StringUtils.isAllEmpty("foo", "bar") = false
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if all of the CharSequences are empty or null
+ * @since 3.6
+ */
+ public static boolean isAllEmpty(final CharSequence... css) {
+ if (ArrayUtils.isEmpty(css)) {
+ return true;
+ }
+ for (final CharSequence cs : css) {
+ if (isNotEmpty(cs)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only lowercase characters.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAllLowerCase(null) = false
+ * StringUtils.isAllLowerCase("") = false
+ * StringUtils.isAllLowerCase(" ") = false
+ * StringUtils.isAllLowerCase("abc") = true
+ * StringUtils.isAllLowerCase("abC") = false
+ * StringUtils.isAllLowerCase("ab c") = false
+ * StringUtils.isAllLowerCase("ab1c") = false
+ * StringUtils.isAllLowerCase("ab/c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains lowercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence)
+ */
+ public static boolean isAllLowerCase(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isLowerCase(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only uppercase characters.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty String (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAllUpperCase(null) = false
+ * StringUtils.isAllUpperCase("") = false
+ * StringUtils.isAllUpperCase(" ") = false
+ * StringUtils.isAllUpperCase("ABC") = true
+ * StringUtils.isAllUpperCase("aBC") = false
+ * StringUtils.isAllUpperCase("A C") = false
+ * StringUtils.isAllUpperCase("A1C") = false
+ * StringUtils.isAllUpperCase("A/C") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains uppercase characters, and is non-null
+ * @since 2.5
+ * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence)
+ */
+ public static boolean isAllUpperCase(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isUpperCase(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAlpha(null) = false
+ * StringUtils.isAlpha("") = false
+ * StringUtils.isAlpha(" ") = false
+ * StringUtils.isAlpha("abc") = true
+ * StringUtils.isAlpha("ab2c") = false
+ * StringUtils.isAlpha("ab-c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, and is non-null
+ * @since 3.0 Changed signature from isAlpha(String) to isAlpha(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlpha(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isLetter(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters or digits.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isAlphanumeric(null) = false
+ * StringUtils.isAlphanumeric("") = false
+ * StringUtils.isAlphanumeric(" ") = false
+ * StringUtils.isAlphanumeric("abc") = true
+ * StringUtils.isAlphanumeric("ab c") = false
+ * StringUtils.isAlphanumeric("ab2c") = true
+ * StringUtils.isAlphanumeric("ab-c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters or digits,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumeric(String) to isAlphanumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isAlphanumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isLetterOrDigit(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters, digits
+ * or space ({@code ' '}).
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isAlphanumericSpace(null) = false
+ * StringUtils.isAlphanumericSpace("") = true
+ * StringUtils.isAlphanumericSpace(" ") = true
+ * StringUtils.isAlphanumericSpace("abc") = true
+ * StringUtils.isAlphanumericSpace("ab c") = true
+ * StringUtils.isAlphanumericSpace("ab2c") = true
+ * StringUtils.isAlphanumericSpace("ab-c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters, digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphanumericSpace(String) to isAlphanumericSpace(CharSequence)
+ */
+ public static boolean isAlphanumericSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ final char nowChar = cs.charAt(i);
+ if (nowChar != ' ' && !Character.isLetterOrDigit(nowChar) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode letters and
+ * space (' ').
+ *
+ * <p>{@code null} will return {@code false}
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isAlphaSpace(null) = false
+ * StringUtils.isAlphaSpace("") = true
+ * StringUtils.isAlphaSpace(" ") = true
+ * StringUtils.isAlphaSpace("abc") = true
+ * StringUtils.isAlphaSpace("ab c") = true
+ * StringUtils.isAlphaSpace("ab2c") = false
+ * StringUtils.isAlphaSpace("ab-c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains letters and space,
+ * and is non-null
+ * @since 3.0 Changed signature from isAlphaSpace(String) to isAlphaSpace(CharSequence)
+ */
+ public static boolean isAlphaSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ final char nowChar = cs.charAt(i);
+ if (nowChar != ' ' && !Character.isLetter(nowChar)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if any of the CharSequences are empty ("") or null or whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.isAnyBlank((String) null) = true
+ * StringUtils.isAnyBlank((String[]) null) = false
+ * StringUtils.isAnyBlank(null, "foo") = true
+ * StringUtils.isAnyBlank(null, null) = true
+ * StringUtils.isAnyBlank("", "bar") = true
+ * StringUtils.isAnyBlank("bob", "") = true
+ * StringUtils.isAnyBlank(" bob ", null) = true
+ * StringUtils.isAnyBlank(" ", "bar") = true
+ * StringUtils.isAnyBlank(new String[] {}) = false
+ * StringUtils.isAnyBlank(new String[]{""}) = true
+ * StringUtils.isAnyBlank("foo", "bar") = false
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if any of the CharSequences are empty or null or whitespace only
+ * @since 3.2
+ */
+ public static boolean isAnyBlank(final CharSequence... css) {
+ if (ArrayUtils.isEmpty(css)) {
+ return false;
+ }
+ for (final CharSequence cs : css) {
+ if (isBlank(cs)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if any of the CharSequences are empty ("") or null.
+ *
+ * <pre>
+ * StringUtils.isAnyEmpty((String) null) = true
+ * StringUtils.isAnyEmpty((String[]) null) = false
+ * StringUtils.isAnyEmpty(null, "foo") = true
+ * StringUtils.isAnyEmpty("", "bar") = true
+ * StringUtils.isAnyEmpty("bob", "") = true
+ * StringUtils.isAnyEmpty(" bob ", null) = true
+ * StringUtils.isAnyEmpty(" ", "bar") = false
+ * StringUtils.isAnyEmpty("foo", "bar") = false
+ * StringUtils.isAnyEmpty(new String[]{}) = false
+ * StringUtils.isAnyEmpty(new String[]{""}) = true
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if any of the CharSequences are empty or null
+ * @since 3.2
+ */
+ public static boolean isAnyEmpty(final CharSequence... css) {
+ if (ArrayUtils.isEmpty(css)) {
+ return false;
+ }
+ for (final CharSequence cs : css) {
+ if (isEmpty(cs)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the CharSequence contains only ASCII printable characters.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isAsciiPrintable(null) = false
+ * StringUtils.isAsciiPrintable("") = true
+ * StringUtils.isAsciiPrintable(" ") = true
+ * StringUtils.isAsciiPrintable("Ceki") = true
+ * StringUtils.isAsciiPrintable("ab2c") = true
+ * StringUtils.isAsciiPrintable("!ab-c~") = true
+ * StringUtils.isAsciiPrintable("\u0020") = true
+ * StringUtils.isAsciiPrintable("\u0021") = true
+ * StringUtils.isAsciiPrintable("\u007e") = true
+ * StringUtils.isAsciiPrintable("\u007f") = false
+ * StringUtils.isAsciiPrintable("Ceki G\u00fclc\u00fc") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if every character is in the range
+ * 32 through 126
+ * @since 2.1
+ * @since 3.0 Changed signature from isAsciiPrintable(String) to isAsciiPrintable(CharSequence)
+ */
+ public static boolean isAsciiPrintable(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!CharUtils.isAsciiPrintable(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if a CharSequence is empty (""), null or whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.isBlank(null) = true
+ * StringUtils.isBlank("") = true
+ * StringUtils.isBlank(" ") = true
+ * StringUtils.isBlank("bob") = false
+ * StringUtils.isBlank(" bob ") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is null, empty or whitespace only
+ * @since 2.0
+ * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence)
+ */
+ public static boolean isBlank(final CharSequence cs) {
+ final int strLen = length(cs);
+ if (strLen == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if a CharSequence is empty ("") or null.
+ *
+ * <pre>
+ * StringUtils.isEmpty(null) = true
+ * StringUtils.isEmpty("") = true
+ * StringUtils.isEmpty(" ") = false
+ * StringUtils.isEmpty("bob") = false
+ * StringUtils.isEmpty(" bob ") = false
+ * </pre>
+ *
+ * <p>NOTE: This method changed in Lang version 2.0.
+ * It no longer trims the CharSequence.
+ * That functionality is available in isBlank().</p>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is empty or null
+ * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)
+ */
+ public static boolean isEmpty(final CharSequence cs) {
+ return cs == null || cs.length() == 0;
+ }
+
+ /**
+ * Checks if the CharSequence contains mixed casing of both uppercase and lowercase characters.
+ *
+ * <p>{@code null} will return {@code false}. An empty CharSequence ({@code length()=0}) will return
+ * {@code false}.</p>
+ *
+ * <pre>
+ * StringUtils.isMixedCase(null) = false
+ * StringUtils.isMixedCase("") = false
+ * StringUtils.isMixedCase(" ") = false
+ * StringUtils.isMixedCase("ABC") = false
+ * StringUtils.isMixedCase("abc") = false
+ * StringUtils.isMixedCase("aBc") = true
+ * StringUtils.isMixedCase("A c") = true
+ * StringUtils.isMixedCase("A1c") = true
+ * StringUtils.isMixedCase("a/C") = true
+ * StringUtils.isMixedCase("aC\t") = true
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence contains both uppercase and lowercase characters
+ * @since 3.5
+ */
+ public static boolean isMixedCase(final CharSequence cs) {
+ if (isEmpty(cs) || cs.length() == 1) {
+ return false;
+ }
+ boolean containsUppercase = false;
+ boolean containsLowercase = false;
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (containsUppercase && containsLowercase) {
+ return true;
+ }
+ if (Character.isUpperCase(cs.charAt(i))) {
+ containsUppercase = true;
+ } else if (Character.isLowerCase(cs.charAt(i))) {
+ containsLowercase = true;
+ }
+ }
+ return containsUppercase && containsLowercase;
+ }
+
+ /**
+ * Checks if none of the CharSequences are empty (""), null or whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.isNoneBlank((String) null) = false
+ * StringUtils.isNoneBlank((String[]) null) = true
+ * StringUtils.isNoneBlank(null, "foo") = false
+ * StringUtils.isNoneBlank(null, null) = false
+ * StringUtils.isNoneBlank("", "bar") = false
+ * StringUtils.isNoneBlank("bob", "") = false
+ * StringUtils.isNoneBlank(" bob ", null) = false
+ * StringUtils.isNoneBlank(" ", "bar") = false
+ * StringUtils.isNoneBlank(new String[] {}) = true
+ * StringUtils.isNoneBlank(new String[]{""}) = false
+ * StringUtils.isNoneBlank("foo", "bar") = true
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if none of the CharSequences are empty or null or whitespace only
+ * @since 3.2
+ */
+ public static boolean isNoneBlank(final CharSequence... css) {
+ return !isAnyBlank(css);
+ }
+
+ /**
+ * Checks if none of the CharSequences are empty ("") or null.
+ *
+ * <pre>
+ * StringUtils.isNoneEmpty((String) null) = false
+ * StringUtils.isNoneEmpty((String[]) null) = true
+ * StringUtils.isNoneEmpty(null, "foo") = false
+ * StringUtils.isNoneEmpty("", "bar") = false
+ * StringUtils.isNoneEmpty("bob", "") = false
+ * StringUtils.isNoneEmpty(" bob ", null) = false
+ * StringUtils.isNoneEmpty(new String[] {}) = true
+ * StringUtils.isNoneEmpty(new String[]{""}) = false
+ * StringUtils.isNoneEmpty(" ", "bar") = true
+ * StringUtils.isNoneEmpty("foo", "bar") = true
+ * </pre>
+ *
+ * @param css the CharSequences to check, may be null or empty
+ * @return {@code true} if none of the CharSequences are empty or null
+ * @since 3.2
+ */
+ public static boolean isNoneEmpty(final CharSequence... css) {
+ return !isAnyEmpty(css);
+ }
+
+ /**
+ * Checks if a CharSequence is not empty (""), not null and not whitespace only.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.isNotBlank(null) = false
+ * StringUtils.isNotBlank("") = false
+ * StringUtils.isNotBlank(" ") = false
+ * StringUtils.isNotBlank("bob") = true
+ * StringUtils.isNotBlank(" bob ") = true
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is
+ * not empty and not null and not whitespace only
+ * @since 2.0
+ * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence)
+ */
+ public static boolean isNotBlank(final CharSequence cs) {
+ return !isBlank(cs);
+ }
+
+ /**
+ * Checks if a CharSequence is not empty ("") and not null.
+ *
+ * <pre>
+ * StringUtils.isNotEmpty(null) = false
+ * StringUtils.isNotEmpty("") = false
+ * StringUtils.isNotEmpty(" ") = true
+ * StringUtils.isNotEmpty("bob") = true
+ * StringUtils.isNotEmpty(" bob ") = true
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is not empty and not null
+ * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence)
+ */
+ public static boolean isNotEmpty(final CharSequence cs) {
+ return !isEmpty(cs);
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode digits.
+ * A decimal point is not a Unicode digit and returns false.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code false}.</p>
+ *
+ * <p>Note that the method does not allow for a leading sign, either positive or negative.
+ * Also, if a String passes the numeric test, it may still generate a NumberFormatException
+ * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range
+ * for int or long respectively.</p>
+ *
+ * <pre>
+ * StringUtils.isNumeric(null) = false
+ * StringUtils.isNumeric("") = false
+ * StringUtils.isNumeric(" ") = false
+ * StringUtils.isNumeric("123") = true
+ * StringUtils.isNumeric("\u0967\u0968\u0969") = true
+ * StringUtils.isNumeric("12 3") = false
+ * StringUtils.isNumeric("ab2c") = false
+ * StringUtils.isNumeric("12-3") = false
+ * StringUtils.isNumeric("12.3") = false
+ * StringUtils.isNumeric("-123") = false
+ * StringUtils.isNumeric("+123") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits, and is non-null
+ * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence)
+ * @since 3.0 Changed "" to return false and not true
+ */
+ public static boolean isNumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isDigit(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only Unicode digits or space
+ * ({@code ' '}).
+ * A decimal point is not a Unicode digit and returns false.
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isNumericSpace(null) = false
+ * StringUtils.isNumericSpace("") = true
+ * StringUtils.isNumericSpace(" ") = true
+ * StringUtils.isNumericSpace("123") = true
+ * StringUtils.isNumericSpace("12 3") = true
+ * StringUtils.isNumericSpace("\u0967\u0968\u0969") = true
+ * StringUtils.isNumericSpace("\u0967\u0968 \u0969") = true
+ * StringUtils.isNumericSpace("ab2c") = false
+ * StringUtils.isNumericSpace("12-3") = false
+ * StringUtils.isNumericSpace("12.3") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains digits or space,
+ * and is non-null
+ * @since 3.0 Changed signature from isNumericSpace(String) to isNumericSpace(CharSequence)
+ */
+ public static boolean isNumericSpace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ final char nowChar = cs.charAt(i);
+ if (nowChar != ' ' && !Character.isDigit(nowChar)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the CharSequence contains only whitespace.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>{@code null} will return {@code false}.
+ * An empty CharSequence (length()=0) will return {@code true}.</p>
+ *
+ * <pre>
+ * StringUtils.isWhitespace(null) = false
+ * StringUtils.isWhitespace("") = true
+ * StringUtils.isWhitespace(" ") = true
+ * StringUtils.isWhitespace("abc") = false
+ * StringUtils.isWhitespace("ab2c") = false
+ * StringUtils.isWhitespace("ab-c") = false
+ * </pre>
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if only contains whitespace, and is non-null
+ * @since 2.0
+ * @since 3.0 Changed signature from isWhitespace(String) to isWhitespace(CharSequence)
+ */
+ public static boolean isWhitespace(final CharSequence cs) {
+ if (cs == null) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isWhitespace(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([false, false], ';') = "false;false"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.12.0
+ */
+ public static String join(final boolean[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([true, false, true], ';') = "true;false;true"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.12.0
+ */
+ public static String join(final boolean[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder(array.length * 5 + array.length - 1);
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final byte[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final byte[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final char[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final char[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder(array.length * 2 - 1);
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final double[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final double[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final float[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final float[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final int[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final int[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided {@link Iterable} into
+ * a single String containing the provided elements.
+ *
+ * <p>No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],char)}.</p>
+ *
+ * @param iterable the {@link Iterable} providing the values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.3
+ */
+ public static String join(final Iterable<?> iterable, final char separator) {
+ return iterable != null ? join(iterable.iterator(), separator) : null;
+ }
+
+ /**
+ * Joins the elements of the provided {@link Iterable} into
+ * a single String containing the provided elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],String)}.</p>
+ *
+ * @param iterable the {@link Iterable} providing the values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.3
+ */
+ public static String join(final Iterable<?> iterable, final String separator) {
+ return iterable != null ? join(iterable.iterator(), separator) : null;
+ }
+
+ /**
+ * Joins the elements of the provided {@link Iterator} into
+ * a single String containing the provided elements.
+ *
+ * <p>No delimiter is added before or after the list. Null objects or empty
+ * strings within the iteration are represented by empty strings.</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],char)}.</p>
+ *
+ * @param iterator the {@link Iterator} of values to join together, may be null
+ * @param separator the separator character to use
+ * @return the joined String, {@code null} if null iterator input
+ * @since 2.0
+ */
+ public static String join(final Iterator<?> iterator, final char separator) {
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ return Streams.of(iterator).collect(LangCollectors.joining(toStringOrEmpty(String.valueOf(separator)), EMPTY, EMPTY, StringUtils::toStringOrEmpty));
+ }
+
+ /**
+ * Joins the elements of the provided {@link Iterator} into
+ * a single String containing the provided elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").</p>
+ *
+ * <p>See the examples here: {@link #join(Object[],String)}.</p>
+ *
+ * @param iterator the {@link Iterator} of values to join together, may be null
+ * @param separator the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null iterator input
+ */
+ public static String join(final Iterator<?> iterator, final String separator) {
+ // handle null, zero and one elements before building a buffer
+ if (iterator == null) {
+ return null;
+ }
+ if (!iterator.hasNext()) {
+ return EMPTY;
+ }
+ return Streams.of(iterator).collect(LangCollectors.joining(toStringOrEmpty(separator), EMPTY, EMPTY, StringUtils::toStringOrEmpty));
+ }
+
+ /**
+ * Joins the elements of the provided {@link List} into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @param list the {@link List} of values to join together, may be null
+ * @param separator the separator character to use
+ * @param startIndex the first index to start joining from. It is
+ * an error to pass in a start index past the end of the list
+ * @param endIndex the index to stop joining from (exclusive). It is
+ * an error to pass in an end index past the end of the list
+ * @return the joined String, {@code null} if null list input
+ * @since 3.8
+ */
+ public static String join(final List<?> list, final char separator, final int startIndex, final int endIndex) {
+ if (list == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final List<?> subList = list.subList(startIndex, endIndex);
+ return join(subList.iterator(), separator);
+ }
+
+ /**
+ * Joins the elements of the provided {@link List} into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @param list the {@link List} of values to join together, may be null
+ * @param separator the separator character to use
+ * @param startIndex the first index to start joining from. It is
+ * an error to pass in a start index past the end of the list
+ * @param endIndex the index to stop joining from (exclusive). It is
+ * an error to pass in an end index past the end of the list
+ * @return the joined String, {@code null} if null list input
+ * @since 3.8
+ */
+ public static String join(final List<?> list, final String separator, final int startIndex, final int endIndex) {
+ if (list == null) {
+ return null;
+ }
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
+ }
+ final List<?> subList = list.subList(startIndex, endIndex);
+ return join(subList.iterator(), separator);
+ }
+
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param separator
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final long[] array, final char separator) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, separator, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final long[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @param array the array of values to join together, may be null
+ * @param delimiter the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ */
+ public static String join(final Object[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], ';') = "a;b;c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join([null, "", "a"], ';') = ";;a"
+ * </pre>
+ *
+ * @param array the array of values to join together, may be null
+ * @param delimiter the separator character to use
+ * @param startIndex the first index to start joining from. It is
+ * an error to pass in a start index past the end of the array
+ * @param endIndex the index to stop joining from (exclusive). It is
+ * an error to pass in an end index past the end of the array
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ */
+ public static String join(final Object[] array, final char delimiter, final int startIndex, final int endIndex) {
+ return join(array, String.valueOf(delimiter), startIndex, endIndex);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], null) = "abc"
+ * StringUtils.join(["a", "b", "c"], "") = "abc"
+ * StringUtils.join([null, "", "a"], ',') = ",,a"
+ * </pre>
+ *
+ * @param array the array of values to join together, may be null
+ * @param delimiter the separator character to use, null treated as ""
+ * @return the joined String, {@code null} if null array input
+ */
+ public static String join(final Object[] array, final String delimiter) {
+ return array != null ? join(array, toStringOrEmpty(delimiter), 0, array.length) : null;
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * A {@code null} separator is the same as an empty String ("").
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null, *, *, *) = null
+ * StringUtils.join([], *, *, *) = ""
+ * StringUtils.join([null], *, *, *) = ""
+ * StringUtils.join(["a", "b", "c"], "--", 0, 3) = "a--b--c"
+ * StringUtils.join(["a", "b", "c"], "--", 1, 3) = "b--c"
+ * StringUtils.join(["a", "b", "c"], "--", 2, 3) = "c"
+ * StringUtils.join(["a", "b", "c"], "--", 2, 2) = ""
+ * StringUtils.join(["a", "b", "c"], null, 0, 3) = "abc"
+ * StringUtils.join(["a", "b", "c"], "", 0, 3) = "abc"
+ * StringUtils.join([null, "", "a"], ',', 0, 3) = ",,a"
+ * </pre>
+ *
+ * @param array the array of values to join together, may be null
+ * @param delimiter the separator character to use, null treated as ""
+ * @param startIndex the first index to start joining from.
+ * @param endIndex the index to stop joining from (exclusive).
+ * @return the joined String, {@code null} if null array input; or the empty string
+ * if {@code endIndex - startIndex <= 0}. The number of joined entries is given by
+ * {@code endIndex - startIndex}
+ * @throws ArrayIndexOutOfBoundsException ife<br>
+ * {@code startIndex < 0} or <br>
+ * {@code startIndex >= array.length()} or <br>
+ * {@code endIndex < 0} or <br>
+ * {@code endIndex > array.length()}
+ */
+ public static String join(final Object[] array, final String delimiter, final int startIndex, final int endIndex) {
+ return array != null ? Streams.of(array).skip(startIndex).limit(Math.max(0, endIndex - startIndex))
+ .collect(LangCollectors.joining(delimiter, EMPTY, EMPTY, StringUtils::toStringOrEmpty)) : null;
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final short[] array, final char delimiter) {
+ if (array == null) {
+ return null;
+ }
+ return join(array, delimiter, 0, array.length);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String containing the provided list of elements.
+ *
+ * <p>
+ * No delimiter is added before or after the list. Null objects or empty strings within the array are represented
+ * by empty strings.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.join(null, *) = null
+ * StringUtils.join([], *) = ""
+ * StringUtils.join([null], *) = ""
+ * StringUtils.join([1, 2, 3], ';') = "1;2;3"
+ * StringUtils.join([1, 2, 3], null) = "123"
+ * </pre>
+ *
+ * @param array
+ * the array of values to join together, may be null
+ * @param delimiter
+ * the separator character to use
+ * @param startIndex
+ * the first index to start joining from. It is an error to pass in a start index past the end of the
+ * array
+ * @param endIndex
+ * the index to stop joining from (exclusive). It is an error to pass in an end index past the end of
+ * the array
+ * @return the joined String, {@code null} if null array input
+ * @since 3.2
+ */
+ public static String join(final short[] array, final char delimiter, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (endIndex - startIndex <= 0) {
+ return EMPTY;
+ }
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder
+ .append(array[i])
+ .append(delimiter);
+ }
+ return stringBuilder.substring(0, stringBuilder.length() - 1);
+ }
+
+ /**
+ * Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ * <p>No separator is added to the joined String.
+ * Null objects or empty strings within the array are represented by
+ * empty strings.</p>
+ *
+ * <pre>
+ * StringUtils.join(null) = null
+ * StringUtils.join([]) = ""
+ * StringUtils.join([null]) = ""
+ * StringUtils.join(["a", "b", "c"]) = "abc"
+ * StringUtils.join([null, "", "a"]) = "a"
+ * </pre>
+ *
+ * @param <T> the specific type of values to join together
+ * @param elements the values to join together, may be null
+ * @return the joined String, {@code null} if null array input
+ * @since 2.0
+ * @since 3.0 Changed signature to use varargs
+ */
+ @SafeVarargs
+ public static <T> String join(final T... elements) {
+ return join(elements, null);
+ }
+
+ /**
+ * Joins the elements of the provided varargs into a
+ * single String containing the provided elements.
+ *
+ * <p>No delimiter is added before or after the list.
+ * {@code null} elements and separator are treated as empty Strings ("").</p>
+ *
+ * <pre>
+ * StringUtils.joinWith(",", {"a", "b"}) = "a,b"
+ * StringUtils.joinWith(",", {"a", "b",""}) = "a,b,"
+ * StringUtils.joinWith(",", {"a", null, "b"}) = "a,,b"
+ * StringUtils.joinWith(null, {"a", "b"}) = "ab"
+ * </pre>
+ *
+ * @param delimiter the separator character to use, null treated as ""
+ * @param array the varargs providing the values to join together. {@code null} elements are treated as ""
+ * @return the joined String.
+ * @throws IllegalArgumentException if a null varargs is provided
+ * @since 3.5
+ */
+ public static String joinWith(final String delimiter, final Object... array) {
+ if (array == null) {
+ throw new IllegalArgumentException("Object varargs must not be null");
+ }
+ return join(array, delimiter);
+ }
+
+ /**
+ * Finds the last index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String)} if possible.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf(*, null) = -1
+ * StringUtils.lastIndexOf("", "") = 0
+ * StringUtils.lastIndexOf("aabaabaa", "a") = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b") = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab") = 4
+ * StringUtils.lastIndexOf("aabaabaa", "") = 8
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @return the last index of the search String,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, String) to lastIndexOf(CharSequence, CharSequence)
+ */
+ public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq) {
+ if (seq == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchSeq, seq.length());
+ }
+
+ /**
+ * Finds the last index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String, int)} if possible.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.
+ * The search starts at the startPos and works backwards; matches starting after the start
+ * position are ignored.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf(*, null, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 8) = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b", 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab", 8) = 4
+ * StringUtils.lastIndexOf("aabaabaa", "b", 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "b", -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.lastIndexOf("aabaabaa", "b", 0) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "b", 1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "b", 2) = 2
+ * StringUtils.lastIndexOf("aabaabaa", "ba", 2) = 2
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchSeq the CharSequence to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the last index of the search CharSequence (always &le; startPos),
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, String, int) to lastIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) {
+ return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos);
+ }
+
+ /**
+ * Returns the index within {@code seq} of the last occurrence of
+ * the specified character. For values of {@code searchChar} in the
+ * range from 0 to 0xFFFF (inclusive), the index (in Unicode code
+ * units) returned is the largest value <i>k</i> such that:
+ * <blockquote><pre>
+ * this.charAt(<i>k</i>) == searchChar
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * largest value <i>k</i> such that:
+ * <blockquote><pre>
+ * this.codePointAt(<i>k</i>) == searchChar
+ * </pre></blockquote>
+ * is true. In either case, if no such character occurs in this
+ * string, then {@code -1} is returned. Furthermore, a {@code null} or empty ("")
+ * {@link CharSequence} will return {@code -1}. The
+ * {@code seq} {@link CharSequence} object is searched backwards
+ * starting at the last character.
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf("", *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
+ * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
+ * </pre>
+ *
+ * @param seq the {@link CharSequence} to check, may be null
+ * @param searchChar the character to find
+ * @return the last index of the search character,
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, int) to lastIndexOf(CharSequence, int)
+ * @since 3.6 Updated {@link CharSequenceUtils} call to behave more like {@link String}
+ */
+ public static int lastIndexOf(final CharSequence seq, final int searchChar) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchChar, seq.length());
+ }
+
+ /**
+ * Returns the index within {@code seq} of the last occurrence of
+ * the specified character, searching backward starting at the
+ * specified index. For values of {@code searchChar} in the range
+ * from 0 to 0xFFFF (inclusive), the index returned is the largest
+ * value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= startPos)
+ * </pre></blockquote>
+ * is true. For other values of {@code searchChar}, it is the
+ * largest value <i>k</i> such that:
+ * <blockquote><pre>
+ * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= startPos)
+ * </pre></blockquote>
+ * is true. In either case, if no such character occurs in {@code seq}
+ * at or before position {@code startPos}, then
+ * {@code -1} is returned. Furthermore, a {@code null} or empty ("")
+ * {@link CharSequence} will return {@code -1}. A start position greater
+ * than the string length searches the whole string.
+ * The search starts at the {@code startPos} and works backwards;
+ * matches starting after the start position are ignored.
+ *
+ * <p>All indices are specified in {@code char} values
+ * (Unicode code units).
+ *
+ * <pre>
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf("", *, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 4) = 2
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 0) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a', 0) = 0
+ * </pre>
+ *
+ * @param seq the CharSequence to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position
+ * @return the last index of the search character (always &le; startPos),
+ * -1 if no match or {@code null} string input
+ * @since 2.0
+ * @since 3.0 Changed signature from lastIndexOf(String, int, int) to lastIndexOf(CharSequence, int, int)
+ */
+ public static int lastIndexOf(final CharSequence seq, final int searchChar, final int startPos) {
+ if (isEmpty(seq)) {
+ return INDEX_NOT_FOUND;
+ }
+ return CharSequenceUtils.lastIndexOf(seq, searchChar, startPos);
+ }
+
+ /**
+ * Find the latest index of any substring in a set of potential substrings.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A {@code null} search array will return {@code -1}.
+ * A {@code null} or zero length search array entry will be ignored,
+ * but a search array containing "" will return the length of {@code str}
+ * if {@code str} is not null. This method uses {@link String#indexOf(String)} if possible</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfAny(null, *) = -1
+ * StringUtils.lastIndexOfAny(*, null) = -1
+ * StringUtils.lastIndexOfAny(*, []) = -1
+ * StringUtils.lastIndexOfAny(*, [null]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["ab", "cd"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["cd", "ab"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", "op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", "op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn", ""]) = 10
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStrs the CharSequences to search for, may be null
+ * @return the last index of any of the CharSequences, -1 if no match
+ * @since 3.0 Changed signature from lastIndexOfAny(String, String[]) to lastIndexOfAny(CharSequence, CharSequence)
+ */
+ public static int lastIndexOfAny(final CharSequence str, final CharSequence... searchStrs) {
+ if (str == null || searchStrs == null) {
+ return INDEX_NOT_FOUND;
+ }
+ int ret = INDEX_NOT_FOUND;
+ int tmp;
+ for (final CharSequence search : searchStrs) {
+ if (search == null) {
+ continue;
+ }
+ tmp = CharSequenceUtils.lastIndexOf(str, search, str.length());
+ if (tmp > ret) {
+ ret = tmp;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Case in-sensitive find of the last index within a CharSequence.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.</p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfIgnoreCase(null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A") = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B") = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @return the first index of the search CharSequence,
+ * -1 if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String) to lastIndexOfIgnoreCase(CharSequence, CharSequence)
+ */
+ public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return lastIndexOfIgnoreCase(str, searchStr, str.length());
+ }
+
+ /**
+ * Case in-sensitive find of the last index within a CharSequence
+ * from the specified position.
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.
+ * A negative start position returns {@code -1}.
+ * An empty ("") search CharSequence always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.
+ * The search starts at the startPos and works backwards; matches starting after the start
+ * position are ignored.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.lastIndexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8) = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0) = -1
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param startPos the start position
+ * @return the last index of the search CharSequence (always &le; startPos),
+ * -1 if no match or {@code null} input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String, int) to lastIndexOfIgnoreCase(CharSequence, CharSequence, int)
+ */
+ public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ final int searchStrLength = searchStr.length();
+ final int strLength = str.length();
+ if (startPos > strLength - searchStrLength) {
+ startPos = strLength - searchStrLength;
+ }
+ if (startPos < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStrLength == 0) {
+ return startPos;
+ }
+
+ for (int i = startPos; i >= 0; i--) {
+ if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStrLength)) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ /**
+ * Finds the n-th last index within a String, handling {@code null}.
+ * This method uses {@link String#lastIndexOf(String)}.
+ *
+ * <p>A {@code null} String will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.lastOrdinalIndexOf(null, *, *) = -1
+ * StringUtils.lastOrdinalIndexOf(*, null, *) = -1
+ * StringUtils.lastOrdinalIndexOf("", "", *) = 0
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1) = 7
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2) = 6
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1) = 5
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2) = 2
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) = 4
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) = 1
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1) = 8
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2) = 8
+ * </pre>
+ *
+ * <p>Note that 'tail(CharSequence str, int n)' may be implemented as: </p>
+ *
+ * <pre>
+ * str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th last {@code searchStr} to find
+ * @return the n-th last index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ * @since 2.5
+ * @since 3.0 Changed signature from lastOrdinalIndexOf(String, String, int) to lastOrdinalIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int lastOrdinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, true);
+ }
+
+ /**
+ * Gets the leftmost {@code len} characters of a String.
+ *
+ * <p>If {@code len} characters are not available, or the
+ * String is {@code null}, the String will be returned without
+ * an exception. An empty String is returned if len is negative.</p>
+ *
+ * <pre>
+ * StringUtils.left(null, *) = null
+ * StringUtils.left(*, -ve) = ""
+ * StringUtils.left("", *) = ""
+ * StringUtils.left("abc", 0) = ""
+ * StringUtils.left("abc", 2) = "ab"
+ * StringUtils.left("abc", 4) = "abc"
+ * </pre>
+ *
+ * @param str the String to get the leftmost characters from, may be null
+ * @param len the length of the required String
+ * @return the leftmost characters, {@code null} if null String input
+ */
+ public static String left(final String str, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(0, len);
+ }
+
+ /**
+ * Left pad a String with spaces (' ').
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.leftPad(null, *) = null
+ * StringUtils.leftPad("", 3) = " "
+ * StringUtils.leftPad("bat", 3) = "bat"
+ * StringUtils.leftPad("bat", 5) = " bat"
+ * StringUtils.leftPad("bat", 1) = "bat"
+ * StringUtils.leftPad("bat", -1) = "bat"
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(final String str, final int size) {
+ return leftPad(str, size, ' ');
+ }
+
+ /**
+ * Left pad a String with a specified character.
+ *
+ * <p>Pad to a size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, 'z') = "zzz"
+ * StringUtils.leftPad("bat", 3, 'z') = "bat"
+ * StringUtils.leftPad("bat", 5, 'z') = "zzbat"
+ * StringUtils.leftPad("bat", 1, 'z') = "bat"
+ * StringUtils.leftPad("bat", -1, 'z') = "bat"
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String leftPad(final String str, final int size, final char padChar) {
+ if (str == null) {
+ return null;
+ }
+ final int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return leftPad(str, size, String.valueOf(padChar));
+ }
+ return repeat(padChar, pads).concat(str);
+ }
+
+ /**
+ * Left pad a String with a specified String.
+ *
+ * <p>Pad to a size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.leftPad(null, *, *) = null
+ * StringUtils.leftPad("", 3, "z") = "zzz"
+ * StringUtils.leftPad("bat", 3, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, "yz") = "yzbat"
+ * StringUtils.leftPad("bat", 8, "yz") = "yzyzybat"
+ * StringUtils.leftPad("bat", 1, "yz") = "bat"
+ * StringUtils.leftPad("bat", -1, "yz") = "bat"
+ * StringUtils.leftPad("bat", 5, null) = " bat"
+ * StringUtils.leftPad("bat", 5, "") = " bat"
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return left padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String leftPad(final String str, final int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int padLen = padStr.length();
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return leftPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return padStr.concat(str);
+ }
+ if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ }
+ final char[] padding = new char[pads];
+ final char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return new String(padding).concat(str);
+ }
+
+ /**
+ * Gets a CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ *
+ * @param cs
+ * a CharSequence or {@code null}
+ * @return CharSequence length or {@code 0} if the CharSequence is
+ * {@code null}.
+ * @since 2.4
+ * @since 3.0 Changed signature from length(String) to length(CharSequence)
+ */
+ public static int length(final CharSequence cs) {
+ return cs == null ? 0 : cs.length();
+ }
+
+ /**
+ * Converts a String to lower case as per {@link String#toLowerCase()}.
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.lowerCase(null) = null
+ * StringUtils.lowerCase("") = ""
+ * StringUtils.lowerCase("aBc") = "abc"
+ * </pre>
+ *
+ * <p><strong>Note:</strong> As described in the documentation for {@link String#toLowerCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #lowerCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).</p>
+ *
+ * @param str the String to lower case, may be null
+ * @return the lower cased String, {@code null} if null String input
+ */
+ public static String lowerCase(final String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase();
+ }
+
+ /**
+ * Converts a String to lower case as per {@link String#toLowerCase(Locale)}.
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.lowerCase(null, Locale.ENGLISH) = null
+ * StringUtils.lowerCase("", Locale.ENGLISH) = ""
+ * StringUtils.lowerCase("aBc", Locale.ENGLISH) = "abc"
+ * </pre>
+ *
+ * @param str the String to lower case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the lower cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String lowerCase(final String str, final Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toLowerCase(LocaleUtils.toLocale(locale));
+ }
+
+ private static int[] matches(final CharSequence first, final CharSequence second) {
+ final CharSequence max;
+ final CharSequence min;
+ if (first.length() > second.length()) {
+ max = first;
+ min = second;
+ } else {
+ max = second;
+ min = first;
+ }
+ final int range = Math.max(max.length() / 2 - 1, 0);
+ final int[] matchIndexes = new int[min.length()];
+ Arrays.fill(matchIndexes, -1);
+ final boolean[] matchFlags = new boolean[max.length()];
+ int matches = 0;
+ for (int mi = 0; mi < min.length(); mi++) {
+ final char c1 = min.charAt(mi);
+ for (int xi = Math.max(mi - range, 0), xn = Math.min(mi + range + 1, max.length()); xi < xn; xi++) {
+ if (!matchFlags[xi] && c1 == max.charAt(xi)) {
+ matchIndexes[mi] = xi;
+ matchFlags[xi] = true;
+ matches++;
+ break;
+ }
+ }
+ }
+ final char[] ms1 = new char[matches];
+ final char[] ms2 = new char[matches];
+ for (int i = 0, si = 0; i < min.length(); i++) {
+ if (matchIndexes[i] != -1) {
+ ms1[si] = min.charAt(i);
+ si++;
+ }
+ }
+ for (int i = 0, si = 0; i < max.length(); i++) {
+ if (matchFlags[i]) {
+ ms2[si] = max.charAt(i);
+ si++;
+ }
+ }
+ int transpositions = 0;
+ for (int mi = 0; mi < ms1.length; mi++) {
+ if (ms1[mi] != ms2[mi]) {
+ transpositions++;
+ }
+ }
+ int prefix = 0;
+ for (int mi = 0; mi < min.length(); mi++) {
+ if (first.charAt(mi) != second.charAt(mi)) {
+ break;
+ }
+ prefix++;
+ }
+ return new int[] { matches, transpositions / 2, prefix, max.length() };
+ }
+
+ /**
+ * Gets {@code len} characters from the middle of a String.
+ *
+ * <p>If {@code len} characters are not available, the remainder
+ * of the String will be returned without an exception. If the
+ * String is {@code null}, {@code null} will be returned.
+ * An empty String is returned if len is negative or exceeds the
+ * length of {@code str}.</p>
+ *
+ * <pre>
+ * StringUtils.mid(null, *, *) = null
+ * StringUtils.mid(*, *, -ve) = ""
+ * StringUtils.mid("", 0, *) = ""
+ * StringUtils.mid("abc", 0, 2) = "ab"
+ * StringUtils.mid("abc", 0, 4) = "abc"
+ * StringUtils.mid("abc", 2, 4) = "c"
+ * StringUtils.mid("abc", 4, 2) = ""
+ * StringUtils.mid("abc", -2, 2) = "ab"
+ * </pre>
+ *
+ * @param str the String to get the characters from, may be null
+ * @param pos the position to start from, negative treated as zero
+ * @param len the length of the required String
+ * @return the middle characters, {@code null} if null String input
+ */
+ public static String mid(final String str, int pos, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0 || pos > str.length()) {
+ return EMPTY;
+ }
+ if (pos < 0) {
+ pos = 0;
+ }
+ if (str.length() <= pos + len) {
+ return str.substring(pos);
+ }
+ return str.substring(pos, pos + len);
+ }
+
+ /**
+ * Similar to <a
+ * href="https://www.w3.org/TR/xpath/#function-normalize-space">https://www.w3.org/TR/xpath/#function-normalize
+ * -space</a>
+ *
+ * <p>
+ * The function returns the argument string with whitespace normalized by using
+ * {@code {@link #trim(String)}} to remove leading and trailing whitespace
+ * and then replacing sequences of whitespace characters by a single space.
+ * </p>
+ * In XML Whitespace characters are the same as those allowed by the <a
+ * href="https://www.w3.org/TR/REC-xml/#NT-S">S</a> production, which is S ::= (#x20 | #x9 | #xD | #xA)+
+ * <p>
+ * Java's regexp pattern \s defines whitespace as [ \t\n\x0B\f\r]
+ *
+ * <p>For reference:</p>
+ * <ul>
+ * <li>\x0B = vertical tab</li>
+ * <li>\f = #xC = form feed</li>
+ * <li>#x20 = space</li>
+ * <li>#x9 = \t</li>
+ * <li>#xA = \n</li>
+ * <li>#xD = \r</li>
+ * </ul>
+ *
+ * <p>
+ * The difference is that Java's whitespace includes vertical tab and form feed, which this functional will also
+ * normalize. Additionally {@code {@link #trim(String)}} removes control characters (char &lt;= 32) from both
+ * ends of this String.
+ * </p>
+ *
+ * @see Pattern
+ * @see #trim(String)
+ * @see <a
+ * href="https://www.w3.org/TR/xpath/#function-normalize-space">https://www.w3.org/TR/xpath/#function-normalize-space</a>
+ * @param str the source String to normalize whitespaces from, may be null
+ * @return the modified string with whitespace normalized, {@code null} if null String input
+ *
+ * @since 3.0
+ */
+ public static String normalizeSpace(final String str) {
+ // LANG-1020: Improved performance significantly by normalizing manually instead of using regex
+ // See https://github.com/librucha/commons-lang-normalizespaces-benchmark for performance test
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int size = str.length();
+ final char[] newChars = new char[size];
+ int count = 0;
+ int whitespacesCount = 0;
+ boolean startWhitespaces = true;
+ for (int i = 0; i < size; i++) {
+ final char actualChar = str.charAt(i);
+ final boolean isWhitespace = Character.isWhitespace(actualChar);
+ if (isWhitespace) {
+ if (whitespacesCount == 0 && !startWhitespaces) {
+ newChars[count++] = SPACE.charAt(0);
+ }
+ whitespacesCount++;
+ } else {
+ startWhitespaces = false;
+ newChars[count++] = actualChar == 160 ? 32 : actualChar;
+ whitespacesCount = 0;
+ }
+ }
+ if (startWhitespaces) {
+ return EMPTY;
+ }
+ return new String(newChars, 0, count - (whitespacesCount > 0 ? 1 : 0)).trim();
+ }
+
+ /**
+ * Finds the n-th index within a CharSequence, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.
+ * <p><b>Note:</b> The code starts looking for a match at the start of the target,
+ * incrementing the starting index by one after each successful match
+ * (unless {@code searchStr} is an empty string in which case the position
+ * is never incremented and {@code 0} is returned immediately).
+ * This means that matches may overlap.</p>
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * <pre>
+ * StringUtils.ordinalIndexOf(null, *, *) = -1
+ * StringUtils.ordinalIndexOf(*, null, *) = -1
+ * StringUtils.ordinalIndexOf("", "", *) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 2) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 1) = 2
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 2) = 5
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 2) = 0
+ * </pre>
+ *
+ * <p>Matches may overlap:</p>
+ * <pre>
+ * StringUtils.ordinalIndexOf("ababab", "aba", 1) = 0
+ * StringUtils.ordinalIndexOf("ababab", "aba", 2) = 2
+ * StringUtils.ordinalIndexOf("ababab", "aba", 3) = -1
+ *
+ * StringUtils.ordinalIndexOf("abababab", "abab", 1) = 0
+ * StringUtils.ordinalIndexOf("abababab", "abab", 2) = 2
+ * StringUtils.ordinalIndexOf("abababab", "abab", 3) = 4
+ * StringUtils.ordinalIndexOf("abababab", "abab", 4) = -1
+ * </pre>
+ *
+ * <p>Note that 'head(CharSequence str, int n)' may be implemented as: </p>
+ *
+ * <pre>
+ * str.substring(0, lastOrdinalIndexOf(str, "\n", n))
+ * </pre>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th {@code searchStr} to find
+ * @return the n-th index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ * @since 2.1
+ * @since 3.0 Changed signature from ordinalIndexOf(String, String, int) to ordinalIndexOf(CharSequence, CharSequence, int)
+ */
+ public static int ordinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, false);
+ }
+
+ /**
+ * Finds the n-th index within a String, handling {@code null}.
+ * This method uses {@link String#indexOf(String)} if possible.
+ * <p>Note that matches may overlap<p>
+ *
+ * <p>A {@code null} CharSequence will return {@code -1}.</p>
+ *
+ * @param str the CharSequence to check, may be null
+ * @param searchStr the CharSequence to find, may be null
+ * @param ordinal the n-th {@code searchStr} to find, overlapping matches are allowed.
+ * @param lastIndex true if lastOrdinalIndexOf() otherwise false if ordinalIndexOf()
+ * @return the n-th index of the search CharSequence,
+ * {@code -1} ({@code INDEX_NOT_FOUND}) if no match or {@code null} string input
+ */
+ // Shared code between ordinalIndexOf(String, String, int) and lastOrdinalIndexOf(String, String, int)
+ private static int ordinalIndexOf(final CharSequence str, final CharSequence searchStr, final int ordinal, final boolean lastIndex) {
+ if (str == null || searchStr == null || ordinal <= 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return lastIndex ? str.length() : 0;
+ }
+ int found = 0;
+ // set the initial index beyond the end of the string
+ // this is to allow for the initial index decrement/increment
+ int index = lastIndex ? str.length() : INDEX_NOT_FOUND;
+ do {
+ if (lastIndex) {
+ index = CharSequenceUtils.lastIndexOf(str, searchStr, index - 1); // step backwards through string
+ } else {
+ index = CharSequenceUtils.indexOf(str, searchStr, index + 1); // step forwards through string
+ }
+ if (index < 0) {
+ return index;
+ }
+ found++;
+ } while (found < ordinal);
+ return index;
+ }
+
+ /**
+ * Overlays part of a String with another String.
+ *
+ * <p>A {@code null} string input returns {@code null}.
+ * A negative index is treated as zero.
+ * An index greater than the string length is treated as the string length.
+ * The start index is always the smaller of the two indices.</p>
+ *
+ * <pre>
+ * StringUtils.overlay(null, *, *, *) = null
+ * StringUtils.overlay("", "abc", 0, 0) = "abc"
+ * StringUtils.overlay("abcdef", null, 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 2, 4) = "abef"
+ * StringUtils.overlay("abcdef", "", 4, 2) = "abef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 4) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 4, 2) = "abzzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", -1, 4) = "zzzzef"
+ * StringUtils.overlay("abcdef", "zzzz", 2, 8) = "abzzzz"
+ * StringUtils.overlay("abcdef", "zzzz", -2, -3) = "zzzzabcdef"
+ * StringUtils.overlay("abcdef", "zzzz", 8, 10) = "abcdefzzzz"
+ * </pre>
+ *
+ * @param str the String to do overlaying in, may be null
+ * @param overlay the String to overlay, may be null
+ * @param start the position to start overlaying at
+ * @param end the position to stop overlaying before
+ * @return overlayed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String overlay(final String str, String overlay, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+ if (overlay == null) {
+ overlay = EMPTY;
+ }
+ final int len = str.length();
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > len) {
+ start = len;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+ if (end > len) {
+ end = len;
+ }
+ if (start > end) {
+ final int temp = start;
+ start = end;
+ end = temp;
+ }
+ return str.substring(0, start) +
+ overlay +
+ str.substring(end);
+ }
+
+ /**
+ * Prepends the prefix to the start of the string if the string does not
+ * already start with any of the prefixes.
+ *
+ * @param str The string.
+ * @param prefix The prefix to prepend to the start of the string.
+ * @param ignoreCase Indicates whether the compare should ignore case.
+ * @param prefixes Additional prefixes that are valid (optional).
+ *
+ * @return A new String if prefix was prepended, the same string otherwise.
+ */
+ private static String prependIfMissing(final String str, final CharSequence prefix, final boolean ignoreCase, final CharSequence... prefixes) {
+ if (str == null || isEmpty(prefix) || startsWith(str, prefix, ignoreCase)) {
+ return str;
+ }
+ if (ArrayUtils.isNotEmpty(prefixes)) {
+ for (final CharSequence p : prefixes) {
+ if (startsWith(str, p, ignoreCase)) {
+ return str;
+ }
+ }
+ }
+ return prefix.toString() + str;
+ }
+
+ /**
+ * Prepends the prefix to the start of the string if the string does not
+ * already start with any of the prefixes.
+ *
+ * <pre>
+ * StringUtils.prependIfMissing(null, null) = null
+ * StringUtils.prependIfMissing("abc", null) = "abc"
+ * StringUtils.prependIfMissing("", "xyz") = "xyz"
+ * StringUtils.prependIfMissing("abc", "xyz") = "xyzabc"
+ * StringUtils.prependIfMissing("xyzabc", "xyz") = "xyzabc"
+ * StringUtils.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc"
+ * </pre>
+ * <p>With additional prefixes,</p>
+ * <pre>
+ * StringUtils.prependIfMissing(null, null, null) = null
+ * StringUtils.prependIfMissing("abc", null, null) = "abc"
+ * StringUtils.prependIfMissing("", "xyz", null) = "xyz"
+ * StringUtils.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
+ * StringUtils.prependIfMissing("abc", "xyz", "") = "abc"
+ * StringUtils.prependIfMissing("abc", "xyz", "mno") = "xyzabc"
+ * StringUtils.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
+ * StringUtils.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
+ * StringUtils.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc"
+ * StringUtils.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc"
+ * </pre>
+ *
+ * @param str The string.
+ * @param prefix The prefix to prepend to the start of the string.
+ * @param prefixes Additional prefixes that are valid.
+ *
+ * @return A new String if prefix was prepended, the same string otherwise.
+ *
+ * @since 3.2
+ */
+ public static String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) {
+ return prependIfMissing(str, prefix, false, prefixes);
+ }
+
+ /**
+ * Prepends the prefix to the start of the string if the string does not
+ * already start, case-insensitive, with any of the prefixes.
+ *
+ * <pre>
+ * StringUtils.prependIfMissingIgnoreCase(null, null) = null
+ * StringUtils.prependIfMissingIgnoreCase("abc", null) = "abc"
+ * StringUtils.prependIfMissingIgnoreCase("", "xyz") = "xyz"
+ * StringUtils.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc"
+ * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc"
+ * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc"
+ * </pre>
+ * <p>With additional prefixes,</p>
+ * <pre>
+ * StringUtils.prependIfMissingIgnoreCase(null, null, null) = null
+ * StringUtils.prependIfMissingIgnoreCase("abc", null, null) = "abc"
+ * StringUtils.prependIfMissingIgnoreCase("", "xyz", null) = "xyz"
+ * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
+ * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc"
+ * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc"
+ * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc"
+ * StringUtils.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc"
+ * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc"
+ * StringUtils.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc"
+ * </pre>
+ *
+ * @param str The string.
+ * @param prefix The prefix to prepend to the start of the string.
+ * @param prefixes Additional prefixes that are valid (optional).
+ *
+ * @return A new String if prefix was prepended, the same string otherwise.
+ *
+ * @since 3.2
+ */
+ public static String prependIfMissingIgnoreCase(final String str, final CharSequence prefix, final CharSequence... prefixes) {
+ return prependIfMissing(str, prefix, true, prefixes);
+ }
+
+ /**
+ * Removes all occurrences of a character from within the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.</p>
+ *
+ * <pre>
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove("queued", 'u') = "qeed"
+ * StringUtils.remove("queued", 'z') = "queued"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the char to search for and remove, may be null
+ * @return the substring with the char removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(final String str, final char remove) {
+ if (isEmpty(str) || str.indexOf(remove) == INDEX_NOT_FOUND) {
+ return str;
+ }
+ final char[] chars = str.toCharArray();
+ int pos = 0;
+ for (int i = 0; i < chars.length; i++) {
+ if (chars[i] != remove) {
+ chars[pos++] = chars[i];
+ }
+ }
+ return new String(chars, 0, pos);
+ }
+
+ /**
+ * Removes all occurrences of a substring from within the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} remove string will return the source string.
+ * An empty ("") remove string will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.remove(null, *) = null
+ * StringUtils.remove("", *) = ""
+ * StringUtils.remove(*, null) = *
+ * StringUtils.remove(*, "") = *
+ * StringUtils.remove("queued", "ue") = "qd"
+ * StringUtils.remove("queued", "zz") = "queued"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String remove(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ return replace(str, remove, EMPTY, -1);
+ }
+
+ /**
+ * Removes each substring of the text String that matches the given regular expression.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceAll(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>Unlike in the {@link #removePattern(String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.removeAll(null, *) = null
+ * StringUtils.removeAll("any", (String) null) = "any"
+ * StringUtils.removeAll("any", "") = "any"
+ * StringUtils.removeAll("any", ".*") = ""
+ * StringUtils.removeAll("any", ".+") = ""
+ * StringUtils.removeAll("abc", ".?") = ""
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "A\nB"
+ * StringUtils.removeAll("A&lt;__&gt;\n&lt;__&gt;B", "(?s)&lt;.*&gt;") = "AB"
+ * StringUtils.removeAll("ABCabc123abc", "[a-z]") = "ABC123"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with any removes processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceAll(String, String, String)
+ * @see #removePattern(String, String)
+ * @see String#replaceAll(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ * @since 3.5
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String removeAll(final String text, final String regex) {
+ return RegExUtils.removeAll(text, regex);
+ }
+
+ /**
+ * Removes a substring only if it is at the end of a source string,
+ * otherwise returns the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.removeEnd(null, *) = null
+ * StringUtils.removeEnd("", *) = ""
+ * StringUtils.removeEnd(*, null) = *
+ * StringUtils.removeEnd("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEnd("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEnd("abc", "") = "abc"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeEnd(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.endsWith(remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Case insensitive removal of a substring if it is at the end of a source string,
+ * otherwise returns the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.removeEndIgnoreCase(null, *) = null
+ * StringUtils.removeEndIgnoreCase("", *) = ""
+ * StringUtils.removeEndIgnoreCase(*, null) = *
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".com") = "www.domain"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeEndIgnoreCase("abc", "") = "abc"
+ * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
+ * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case-insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeEndIgnoreCase(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (endsWithIgnoreCase(str, remove)) {
+ return str.substring(0, str.length() - remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Removes the first substring of the text string that matches the given regular expression.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceFirst(regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceFirst(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.removeFirst(null, *) = null
+ * StringUtils.removeFirst("any", (String) null) = "any"
+ * StringUtils.removeFirst("any", "") = "any"
+ * StringUtils.removeFirst("any", ".*") = ""
+ * StringUtils.removeFirst("any", ".+") = ""
+ * StringUtils.removeFirst("abc", ".?") = "bc"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "A\n&lt;__&gt;B"
+ * StringUtils.removeFirst("A&lt;__&gt;\n&lt;__&gt;B", "(?s)&lt;.*&gt;") = "AB"
+ * StringUtils.removeFirst("ABCabc123", "[a-z]") = "ABCbc123"
+ * StringUtils.removeFirst("ABCabc123abc", "[a-z]+") = "ABC123abc"
+ * </pre>
+ *
+ * @param text text to remove from, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replaceFirst(String, String, String)
+ * @see String#replaceFirst(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ * @since 3.5
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String removeFirst(final String text, final String regex) {
+ return replaceFirst(text, regex, EMPTY);
+ }
+
+ /**
+ * Case insensitive removal of all occurrences of a substring from within
+ * the source string.
+ *
+ * <p>
+ * A {@code null} source string will return {@code null}. An empty ("")
+ * source string will return the empty string. A {@code null} remove string
+ * will return the source string. An empty ("") remove string will return
+ * the source string.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.removeIgnoreCase(null, *) = null
+ * StringUtils.removeIgnoreCase("", *) = ""
+ * StringUtils.removeIgnoreCase(*, null) = *
+ * StringUtils.removeIgnoreCase(*, "") = *
+ * StringUtils.removeIgnoreCase("queued", "ue") = "qd"
+ * StringUtils.removeIgnoreCase("queued", "zz") = "queued"
+ * StringUtils.removeIgnoreCase("quEUed", "UE") = "qd"
+ * StringUtils.removeIgnoreCase("queued", "zZ") = "queued"
+ * </pre>
+ *
+ * @param str
+ * the source String to search, may be null
+ * @param remove
+ * the String to search for (case-insensitive) and remove, may be
+ * null
+ * @return the substring with the string removed if found, {@code null} if
+ * null String input
+ * @since 3.5
+ */
+ public static String removeIgnoreCase(final String str, final String remove) {
+ return replaceIgnoreCase(str, remove, EMPTY, -1);
+ }
+
+ /**
+ * Removes each substring of the source String that matches the given regular expression using the DOTALL option.
+ *
+ * This call is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code source.replaceAll(&quot;(?s)&quot; + regex, StringUtils.EMPTY)}</li>
+ * <li>{@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(StringUtils.EMPTY)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.removePattern(null, *) = null
+ * StringUtils.removePattern("any", (String) null) = "any"
+ * StringUtils.removePattern("A&lt;__&gt;\n&lt;__&gt;B", "&lt;.*&gt;") = "AB"
+ * StringUtils.removePattern("ABCabc123", "[a-z]") = "ABC123"
+ * </pre>
+ *
+ * @param source
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @return The resulting {@link String}
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ * @since 3.2
+ * @since 3.5 Changed {@code null} reference passed to this method is a no-op.
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String removePattern(final String source, final String regex) {
+ return RegExUtils.removePattern(source, regex);
+ }
+
+ /**
+ * Removes a char only if it is at the beginning of a source string,
+ * otherwise returns the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search char will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.removeStart(null, *) = null
+ * StringUtils.removeStart("", *) = ""
+ * StringUtils.removeStart(*, null) = *
+ * StringUtils.removeStart("/path", '/') = "path"
+ * StringUtils.removeStart("path", '/') = "path"
+ * StringUtils.removeStart("path", 0) = "path"
+ * </pre>
+ *
+ * @param str the source String to search, may be null.
+ * @param remove the char to search for and remove.
+ * @return the substring with the char removed if found,
+ * {@code null} if null String input.
+ * @since 3.13.0
+ */
+ public static String removeStart(final String str, final char remove) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ return str.charAt(0) == remove ? str.substring(1) : str;
+ }
+
+ /**
+ * Removes a substring only if it is at the beginning of a source string,
+ * otherwise returns the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.removeStart(null, *) = null
+ * StringUtils.removeStart("", *) = ""
+ * StringUtils.removeStart(*, null) = *
+ * StringUtils.removeStart("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStart("abc", "") = "abc"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.1
+ */
+ public static String removeStart(final String str, final String remove) {
+ if (isEmpty(str) || isEmpty(remove)) {
+ return str;
+ }
+ if (str.startsWith(remove)) {
+ return str.substring(remove.length());
+ }
+ return str;
+ }
+
+ /**
+ * Case insensitive removal of a substring if it is at the beginning of a source string,
+ * otherwise returns the source string.
+ *
+ * <p>A {@code null} source string will return {@code null}.
+ * An empty ("") source string will return the empty string.
+ * A {@code null} search string will return the source string.</p>
+ *
+ * <pre>
+ * StringUtils.removeStartIgnoreCase(null, *) = null
+ * StringUtils.removeStartIgnoreCase("", *) = ""
+ * StringUtils.removeStartIgnoreCase(*, null) = *
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("domain.com", "www.") = "domain.com"
+ * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
+ * StringUtils.removeStartIgnoreCase("abc", "") = "abc"
+ * </pre>
+ *
+ * @param str the source String to search, may be null
+ * @param remove the String to search for (case-insensitive) and remove, may be null
+ * @return the substring with the string removed if found,
+ * {@code null} if null String input
+ * @since 2.4
+ */
+ public static String removeStartIgnoreCase(final String str, final String remove) {
+ if (str != null && startsWithIgnoreCase(str, remove)) {
+ return str.substring(length(remove));
+ }
+ return str;
+ }
+
+ /**
+ * Returns padding using the specified delimiter repeated
+ * to a given length.
+ *
+ * <pre>
+ * StringUtils.repeat('e', 0) = ""
+ * StringUtils.repeat('e', 3) = "eee"
+ * StringUtils.repeat('e', -2) = ""
+ * </pre>
+ *
+ * <p>Note: this method does not support padding with
+ * <a href="https://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary Characters</a>
+ * as they require a pair of {@code char}s to be represented.
+ * If you are needing to support full I18N of your applications
+ * consider using {@link #repeat(String, int)} instead.
+ * </p>
+ *
+ * @param ch character to repeat
+ * @param repeat number of times to repeat char, negative treated as zero
+ * @return String with repeated character
+ * @see #repeat(String, int)
+ */
+ public static String repeat(final char ch, final int repeat) {
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ final char[] buf = new char[repeat];
+ Arrays.fill(buf, ch);
+ return new String(buf);
+ }
+
+ /**
+ * Repeat a String {@code repeat} times to form a
+ * new String.
+ *
+ * <pre>
+ * StringUtils.repeat(null, 2) = null
+ * StringUtils.repeat("", 0) = ""
+ * StringUtils.repeat("", 2) = ""
+ * StringUtils.repeat("a", 3) = "aaa"
+ * StringUtils.repeat("ab", 2) = "abab"
+ * StringUtils.repeat("a", -2) = ""
+ * </pre>
+ *
+ * @param str the String to repeat, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ */
+ public static String repeat(final String str, final int repeat) {
+ // Performance tuned for 2.0 (JDK1.4)
+ if (str == null) {
+ return null;
+ }
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ final int inputLength = str.length();
+ if (repeat == 1 || inputLength == 0) {
+ return str;
+ }
+ if (inputLength == 1 && repeat <= PAD_LIMIT) {
+ return repeat(str.charAt(0), repeat);
+ }
+
+ final int outputLength = inputLength * repeat;
+ switch (inputLength) {
+ case 1 :
+ return repeat(str.charAt(0), repeat);
+ case 2 :
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char[] output2 = new char[outputLength];
+ for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
+ output2[i] = ch0;
+ output2[i + 1] = ch1;
+ }
+ return new String(output2);
+ default :
+ final StringBuilder buf = new StringBuilder(outputLength);
+ for (int i = 0; i < repeat; i++) {
+ buf.append(str);
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Repeat a String {@code repeat} times to form a
+ * new String, with a String separator injected each time.
+ *
+ * <pre>
+ * StringUtils.repeat(null, null, 2) = null
+ * StringUtils.repeat(null, "x", 2) = null
+ * StringUtils.repeat("", null, 0) = ""
+ * StringUtils.repeat("", "", 2) = ""
+ * StringUtils.repeat("", "x", 3) = "xx"
+ * StringUtils.repeat("?", ", ", 3) = "?, ?, ?"
+ * </pre>
+ *
+ * @param str the String to repeat, may be null
+ * @param separator the String to inject, may be null
+ * @param repeat number of times to repeat str, negative treated as zero
+ * @return a new String consisting of the original String repeated,
+ * {@code null} if null String input
+ * @since 2.5
+ */
+ public static String repeat(final String str, final String separator, final int repeat) {
+ if (str == null || separator == null) {
+ return repeat(str, repeat);
+ }
+ // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it
+ final String result = repeat(str + separator, repeat);
+ return removeEnd(result, separator);
+ }
+
+ /**
+ * Replaces all occurrences of a String within another String.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replace(null, *, *) = null
+ * StringUtils.replace("", *, *) = ""
+ * StringUtils.replace("any", null, *) = "any"
+ * StringUtils.replace("any", *, null) = "any"
+ * StringUtils.replace("any", "", *) = "any"
+ * StringUtils.replace("aba", "a", null) = "aba"
+ * StringUtils.replace("aba", "a", "") = "b"
+ * StringUtils.replace("aba", "a", "z") = "zbz"
+ * </pre>
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(final String text, final String searchString, final String replacement) {
+ return replace(text, searchString, replacement, -1);
+ }
+
+ /**
+ * Replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replace(null, *, *, *) = null
+ * StringUtils.replace("", *, *, *) = ""
+ * StringUtils.replace("any", null, *, *) = "any"
+ * StringUtils.replace("any", *, null, *) = "any"
+ * StringUtils.replace("any", "", *, *) = "any"
+ * StringUtils.replace("any", *, *, 0) = "any"
+ * StringUtils.replace("abaa", "a", null, -1) = "abaa"
+ * StringUtils.replace("abaa", "a", "", -1) = "b"
+ * StringUtils.replace("abaa", "a", "z", 0) = "abaa"
+ * StringUtils.replace("abaa", "a", "z", 1) = "zbaa"
+ * StringUtils.replace("abaa", "a", "z", 2) = "zbza"
+ * StringUtils.replace("abaa", "a", "z", -1) = "zbzz"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace it with, may be null
+ * @param max maximum number of values to replace, or {@code -1} if no maximum
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replace(final String text, final String searchString, final String replacement, final int max) {
+ return replace(text, searchString, replacement, max, false);
+ }
+
+ /**
+ * Replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String,
+ * case-sensitively/insensitively based on {@code ignoreCase} value.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replace(null, *, *, *, false) = null
+ * StringUtils.replace("", *, *, *, false) = ""
+ * StringUtils.replace("any", null, *, *, false) = "any"
+ * StringUtils.replace("any", *, null, *, false) = "any"
+ * StringUtils.replace("any", "", *, *, false) = "any"
+ * StringUtils.replace("any", *, *, 0, false) = "any"
+ * StringUtils.replace("abaa", "a", null, -1, false) = "abaa"
+ * StringUtils.replace("abaa", "a", "", -1, false) = "b"
+ * StringUtils.replace("abaa", "a", "z", 0, false) = "abaa"
+ * StringUtils.replace("abaa", "A", "z", 1, false) = "abaa"
+ * StringUtils.replace("abaa", "A", "z", 1, true) = "zbaa"
+ * StringUtils.replace("abAa", "a", "z", 2, true) = "zbza"
+ * StringUtils.replace("abAa", "a", "z", -1, true) = "zbzz"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for (case-insensitive), may be null
+ * @param replacement the String to replace it with, may be null
+ * @param max maximum number of values to replace, or {@code -1} if no maximum
+ * @param ignoreCase if true replace is case-insensitive, otherwise case-sensitive
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ private static String replace(final String text, String searchString, final String replacement, int max, final boolean ignoreCase) {
+ if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
+ return text;
+ }
+ if (ignoreCase) {
+ searchString = searchString.toLowerCase();
+ }
+ int start = 0;
+ int end = ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start);
+ if (end == INDEX_NOT_FOUND) {
+ return text;
+ }
+ final int replLength = searchString.length();
+ int increase = Math.max(replacement.length() - replLength, 0);
+ increase *= max < 0 ? 16 : Math.min(max, 64);
+ final StringBuilder buf = new StringBuilder(text.length() + increase);
+ while (end != INDEX_NOT_FOUND) {
+ buf.append(text, start, end).append(replacement);
+ start = end + replLength;
+ if (--max == 0) {
+ break;
+ }
+ end = ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start);
+ }
+ buf.append(text, start, text.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces each substring of the text String that matches the given regular expression
+ * with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceAll(regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceAll(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>Unlike in the {@link #replacePattern(String, String, String)} method, the {@link Pattern#DOTALL} option
+ * is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.replaceAll(null, *, *) = null
+ * StringUtils.replaceAll("any", (String) null, *) = "any"
+ * StringUtils.replaceAll("any", *, null) = "any"
+ * StringUtils.replaceAll("", "", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceAll("", ".+", "zzz") = ""
+ * StringUtils.replaceAll("abc", "", "ZZ") = "ZZaZZbZZcZZ"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z\nz"
+ * StringUtils.replaceAll("&lt;__&gt;\n&lt;__&gt;", "(?s)&lt;.*&gt;", "z") = "z"
+ * StringUtils.replaceAll("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replaceAll("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replaceAll("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for each match
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see #replacePattern(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ * @since 3.5
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String replaceAll(final String text, final String regex, final String replacement) {
+ return RegExUtils.replaceAll(text, regex, replacement);
+ }
+
+ /**
+ * Replaces all occurrences of a character in a String with another.
+ * This is a null-safe version of {@link String#replace(char, char)}.
+ *
+ * <p>A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.</p>
+ *
+ * <pre>
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
+ * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
+ * </pre>
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChar the character to search for, may be null
+ * @param replaceChar the character to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(final String str, final char searchChar, final char replaceChar) {
+ if (str == null) {
+ return null;
+ }
+ return str.replace(searchChar, replaceChar);
+ }
+
+ /**
+ * Replaces multiple characters in a String in one go.
+ * This method can also be used to delete characters.
+ *
+ * <p>For example:<br>
+ * {@code replaceChars(&quot;hello&quot;, &quot;ho&quot;, &quot;jy&quot;) = jelly}.</p>
+ *
+ * <p>A {@code null} string input returns {@code null}.
+ * An empty ("") string input returns an empty string.
+ * A null or empty set of search characters returns the input string.</p>
+ *
+ * <p>The length of the search characters should normally equal the length
+ * of the replace characters.
+ * If the search characters is longer, then the extra search characters
+ * are deleted.
+ * If the search characters is shorter, then the extra replace characters
+ * are ignored.</p>
+ *
+ * <pre>
+ * StringUtils.replaceChars(null, *, *) = null
+ * StringUtils.replaceChars("", *, *) = ""
+ * StringUtils.replaceChars("abc", null, *) = "abc"
+ * StringUtils.replaceChars("abc", "", *) = "abc"
+ * StringUtils.replaceChars("abc", "b", null) = "ac"
+ * StringUtils.replaceChars("abc", "b", "") = "ac"
+ * StringUtils.replaceChars("abcba", "bc", "yz") = "ayzya"
+ * StringUtils.replaceChars("abcba", "bc", "y") = "ayya"
+ * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
+ * </pre>
+ *
+ * @param str String to replace characters in, may be null
+ * @param searchChars a set of characters to search for, may be null
+ * @param replaceChars a set of characters to replace, may be null
+ * @return modified String, {@code null} if null string input
+ * @since 2.0
+ */
+ public static String replaceChars(final String str, final String searchChars, String replaceChars) {
+ if (isEmpty(str) || isEmpty(searchChars)) {
+ return str;
+ }
+ if (replaceChars == null) {
+ replaceChars = EMPTY;
+ }
+ boolean modified = false;
+ final int replaceCharsLength = replaceChars.length();
+ final int strLength = str.length();
+ final StringBuilder buf = new StringBuilder(strLength);
+ for (int i = 0; i < strLength; i++) {
+ final char ch = str.charAt(i);
+ final int index = searchChars.indexOf(ch);
+ if (index >= 0) {
+ modified = true;
+ if (index < replaceCharsLength) {
+ buf.append(replaceChars.charAt(index));
+ }
+ } else {
+ buf.append(ch);
+ }
+ }
+ if (modified) {
+ return buf.toString();
+ }
+ return str;
+ }
+
+ /**
+ * Replaces all occurrences of Strings within another String.
+ *
+ * <p>
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored. This will not repeat. For repeating replaces, call the
+ * overloaded method.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEach(null, *, *) = null
+ * StringUtils.replaceEach("", *, *) = ""
+ * StringUtils.replaceEach("aba", null, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
+ * (example of how it does not repeat)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
+ return replaceEach(text, searchList, replacementList, false, 0);
+ }
+
+ /**
+ * Replace all occurrences of Strings within another String.
+ * This is a private recursive helper method for {@link #replaceEachRepeatedly(String, String[], String[])} and
+ * {@link #replaceEach(String, String[], String[])}
+ *
+ * <p>
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEach(null, *, *, *, *) = null
+ * StringUtils.replaceEach("", *, *, *, *) = ""
+ * StringUtils.replaceEach("aba", null, null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[0], null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", null, new String[0], *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, null, *, *) = "aba"
+ * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *, >=0) = "b"
+ * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *, >=0) = "aba"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *, >=0) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false, >=0) = "dcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true, >=2) = "tcte"
+ * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *, *) = IllegalStateException
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @param repeat if true, then replace repeatedly
+ * until there are no more possible replacements or timeToLive < 0
+ * @param timeToLive
+ * if less than 0 then there is a circular reference and endless
+ * loop
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ private static String replaceEach(
+ final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) {
+
+ // mchyzer Performance note: This creates very few new objects (one major goal)
+ // let me know if there are performance requests, we can create a harness to measure
+
+ // if recursing, this shouldn't be less than 0
+ if (timeToLive < 0) {
+ final Set<String> searchSet = new HashSet<>(Arrays.asList(searchList));
+ final Set<String> replacementSet = new HashSet<>(Arrays.asList(replacementList));
+ searchSet.retainAll(replacementSet);
+ if (!searchSet.isEmpty()) {
+ throw new IllegalStateException("Aborting to protect against StackOverflowError - " +
+ "output of one loop is the input of another");
+ }
+ }
+
+ if (isEmpty(text) || ArrayUtils.isEmpty(searchList) || ArrayUtils.isEmpty(replacementList) || ArrayUtils.isNotEmpty(searchList) && timeToLive == -1) {
+ return text;
+ }
+
+ final int searchLength = searchList.length;
+ final int replacementLength = replacementList.length;
+
+ // make sure lengths are ok, these need to be equal
+ if (searchLength != replacementLength) {
+ throw new IllegalArgumentException("Search and Replace array lengths don't match: "
+ + searchLength
+ + " vs "
+ + replacementLength);
+ }
+
+ // keep track of which still have matches
+ final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
+
+ // index on index that the match was found
+ int textIndex = -1;
+ int replaceIndex = -1;
+ int tempIndex;
+
+ // index of replace array that will replace the search string found
+ // NOTE: logic duplicated below START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || isEmpty(searchList[i]) || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i]);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ // NOTE: logic mostly below END
+
+ // no search strings found, we are done
+ if (textIndex == -1) {
+ return text;
+ }
+
+ int start = 0;
+
+ // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
+ int increase = 0;
+
+ // count the replacement text elements that are larger than their corresponding text being replaced
+ for (int i = 0; i < searchList.length; i++) {
+ if (searchList[i] == null || replacementList[i] == null) {
+ continue;
+ }
+ final int greater = replacementList[i].length() - searchList[i].length();
+ if (greater > 0) {
+ increase += 3 * greater; // assume 3 matches
+ }
+ }
+ // have upper-bound at 20% increase, then let Java take over
+ increase = Math.min(increase, text.length() / 5);
+
+ final StringBuilder buf = new StringBuilder(text.length() + increase);
+
+ while (textIndex != -1) {
+
+ for (int i = start; i < textIndex; i++) {
+ buf.append(text.charAt(i));
+ }
+ buf.append(replacementList[replaceIndex]);
+
+ start = textIndex + searchList[replaceIndex].length();
+
+ textIndex = -1;
+ replaceIndex = -1;
+ // find the next earliest match
+ // NOTE: logic mostly duplicated above START
+ for (int i = 0; i < searchLength; i++) {
+ if (noMoreMatchesForReplIndex[i] || isEmpty(searchList[i]) || replacementList[i] == null) {
+ continue;
+ }
+ tempIndex = text.indexOf(searchList[i], start);
+
+ // see if we need to keep searching for this
+ if (tempIndex == -1) {
+ noMoreMatchesForReplIndex[i] = true;
+ } else if (textIndex == -1 || tempIndex < textIndex) {
+ textIndex = tempIndex;
+ replaceIndex = i;
+ }
+ }
+ // NOTE: logic duplicated above END
+
+ }
+ final int textLength = text.length();
+ for (int i = start; i < textLength; i++) {
+ buf.append(text.charAt(i));
+ }
+ final String result = buf.toString();
+ if (!repeat) {
+ return result;
+ }
+
+ return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
+ }
+
+ /**
+ * Replaces all occurrences of Strings within another String.
+ *
+ * <p>
+ * A {@code null} reference passed to this method is a no-op, or if
+ * any "search string" or "string to replace" is null, that replace will be
+ * ignored.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.replaceEachRepeatedly(null, *, *) = null
+ * StringUtils.replaceEachRepeatedly("", *, *) = ""
+ * StringUtils.replaceEachRepeatedly("aba", null, null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[0], null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", null, new String[0]) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, null) = "aba"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{"a"}, new String[]{""}) = "b"
+ * StringUtils.replaceEachRepeatedly("aba", new String[]{null}, new String[]{"a"}) = "aba"
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
+ * (example of how it repeats)
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "tcte"
+ * StringUtils.replaceEachRepeatedly("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}) = IllegalStateException
+ * </pre>
+ *
+ * @param text
+ * text to search and replace in, no-op if null
+ * @param searchList
+ * the Strings to search for, no-op if null
+ * @param replacementList
+ * the Strings to replace them with, no-op if null
+ * @return the text with any replacements processed, {@code null} if
+ * null String input
+ * @throws IllegalStateException
+ * if the search is repeating and there is an endless loop due
+ * to outputs of one being inputs to another
+ * @throws IllegalArgumentException
+ * if the lengths of the arrays are not the same (null is ok,
+ * and/or size 0)
+ * @since 2.4
+ */
+ public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) {
+ return replaceEach(text, searchList, replacementList, true, ArrayUtils.getLength(searchList));
+ }
+
+ /**
+ * Replaces the first substring of the text string that matches the given regular expression
+ * with the given replacement.
+ *
+ * This method is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code text.replaceFirst(regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex).matcher(text).replaceFirst(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <p>The {@link Pattern#DOTALL} option is NOT automatically added.
+ * To use the DOTALL option prepend {@code "(?s)"} to the regex.
+ * DOTALL is also known as single-line mode in Perl.</p>
+ *
+ * <pre>
+ * StringUtils.replaceFirst(null, *, *) = null
+ * StringUtils.replaceFirst("any", (String) null, *) = "any"
+ * StringUtils.replaceFirst("any", *, null) = "any"
+ * StringUtils.replaceFirst("", "", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".*", "zzz") = "zzz"
+ * StringUtils.replaceFirst("", ".+", "zzz") = ""
+ * StringUtils.replaceFirst("abc", "", "ZZ") = "ZZabc"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z\n&lt;__&gt;"
+ * StringUtils.replaceFirst("&lt;__&gt;\n&lt;__&gt;", "(?s)&lt;.*&gt;", "z") = "z"
+ * StringUtils.replaceFirst("ABCabc123", "[a-z]", "_") = "ABC_bc123"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "_") = "ABC_123abc"
+ * StringUtils.replaceFirst("ABCabc123abc", "[^A-Z0-9]+", "") = "ABC123abc"
+ * StringUtils.replaceFirst("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum dolor sit"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param regex the regular expression to which this string is to be matched
+ * @param replacement the string to be substituted for the first match
+ * @return the text with the first replacement processed,
+ * {@code null} if null String input
+ *
+ * @throws java.util.regex.PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ *
+ * @see String#replaceFirst(String, String)
+ * @see java.util.regex.Pattern
+ * @see java.util.regex.Pattern#DOTALL
+ * @since 3.5
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String replaceFirst(final String text, final String regex, final String replacement) {
+ return RegExUtils.replaceFirst(text, regex, replacement);
+ }
+
+ /**
+ * Case insensitively replaces all occurrences of a String within another String.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceIgnoreCase(null, *, *) = null
+ * StringUtils.replaceIgnoreCase("", *, *) = ""
+ * StringUtils.replaceIgnoreCase("any", null, *) = "any"
+ * StringUtils.replaceIgnoreCase("any", *, null) = "any"
+ * StringUtils.replaceIgnoreCase("any", "", *) = "any"
+ * StringUtils.replaceIgnoreCase("aba", "a", null) = "aba"
+ * StringUtils.replaceIgnoreCase("abA", "A", "") = "b"
+ * StringUtils.replaceIgnoreCase("aba", "A", "z") = "zbz"
+ * </pre>
+ *
+ * @see #replaceIgnoreCase(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for (case-insensitive), may be null
+ * @param replacement the String to replace it with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ * @since 3.5
+ */
+ public static String replaceIgnoreCase(final String text, final String searchString, final String replacement) {
+ return replaceIgnoreCase(text, searchString, replacement, -1);
+ }
+
+ /**
+ * Case insensitively replaces a String with another String inside a larger String,
+ * for the first {@code max} values of the search String.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceIgnoreCase(null, *, *, *) = null
+ * StringUtils.replaceIgnoreCase("", *, *, *) = ""
+ * StringUtils.replaceIgnoreCase("any", null, *, *) = "any"
+ * StringUtils.replaceIgnoreCase("any", *, null, *) = "any"
+ * StringUtils.replaceIgnoreCase("any", "", *, *) = "any"
+ * StringUtils.replaceIgnoreCase("any", *, *, 0) = "any"
+ * StringUtils.replaceIgnoreCase("abaa", "a", null, -1) = "abaa"
+ * StringUtils.replaceIgnoreCase("abaa", "a", "", -1) = "b"
+ * StringUtils.replaceIgnoreCase("abaa", "a", "z", 0) = "abaa"
+ * StringUtils.replaceIgnoreCase("abaa", "A", "z", 1) = "zbaa"
+ * StringUtils.replaceIgnoreCase("abAa", "a", "z", 2) = "zbza"
+ * StringUtils.replaceIgnoreCase("abAa", "a", "z", -1) = "zbzz"
+ * </pre>
+ *
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for (case-insensitive), may be null
+ * @param replacement the String to replace it with, may be null
+ * @param max maximum number of values to replace, or {@code -1} if no maximum
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ * @since 3.5
+ */
+ public static String replaceIgnoreCase(final String text, final String searchString, final String replacement, final int max) {
+ return replace(text, searchString, replacement, max, true);
+ }
+
+ /**
+ * Replaces a String with another String inside a larger String, once.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceOnce(null, *, *) = null
+ * StringUtils.replaceOnce("", *, *) = ""
+ * StringUtils.replaceOnce("any", null, *) = "any"
+ * StringUtils.replaceOnce("any", *, null) = "any"
+ * StringUtils.replaceOnce("any", "", *) = "any"
+ * StringUtils.replaceOnce("aba", "a", null) = "aba"
+ * StringUtils.replaceOnce("aba", "a", "") = "ba"
+ * StringUtils.replaceOnce("aba", "a", "z") = "zba"
+ * </pre>
+ *
+ * @see #replace(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for, may be null
+ * @param replacement the String to replace with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ */
+ public static String replaceOnce(final String text, final String searchString, final String replacement) {
+ return replace(text, searchString, replacement, 1);
+ }
+
+ /**
+ * Case insensitively replaces a String with another String inside a larger String, once.
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replaceOnceIgnoreCase(null, *, *) = null
+ * StringUtils.replaceOnceIgnoreCase("", *, *) = ""
+ * StringUtils.replaceOnceIgnoreCase("any", null, *) = "any"
+ * StringUtils.replaceOnceIgnoreCase("any", *, null) = "any"
+ * StringUtils.replaceOnceIgnoreCase("any", "", *) = "any"
+ * StringUtils.replaceOnceIgnoreCase("aba", "a", null) = "aba"
+ * StringUtils.replaceOnceIgnoreCase("aba", "a", "") = "ba"
+ * StringUtils.replaceOnceIgnoreCase("aba", "a", "z") = "zba"
+ * StringUtils.replaceOnceIgnoreCase("FoOFoofoo", "foo", "") = "Foofoo"
+ * </pre>
+ *
+ * @see #replaceIgnoreCase(String text, String searchString, String replacement, int max)
+ * @param text text to search and replace in, may be null
+ * @param searchString the String to search for (case-insensitive), may be null
+ * @param replacement the String to replace with, may be null
+ * @return the text with any replacements processed,
+ * {@code null} if null String input
+ * @since 3.5
+ */
+ public static String replaceOnceIgnoreCase(final String text, final String searchString, final String replacement) {
+ return replaceIgnoreCase(text, searchString, replacement, 1);
+ }
+
+ /**
+ * Replaces each substring of the source String that matches the given regular expression with the given
+ * replacement using the {@link Pattern#DOTALL} option. DOTALL is also known as single-line mode in Perl.
+ *
+ * This call is a {@code null} safe equivalent to:
+ * <ul>
+ * <li>{@code source.replaceAll(&quot;(?s)&quot; + regex, replacement)}</li>
+ * <li>{@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement)}</li>
+ * </ul>
+ *
+ * <p>A {@code null} reference passed to this method is a no-op.</p>
+ *
+ * <pre>
+ * StringUtils.replacePattern(null, *, *) = null
+ * StringUtils.replacePattern("any", (String) null, *) = "any"
+ * StringUtils.replacePattern("any", *, null) = "any"
+ * StringUtils.replacePattern("", "", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".*", "zzz") = "zzz"
+ * StringUtils.replacePattern("", ".+", "zzz") = ""
+ * StringUtils.replacePattern("&lt;__&gt;\n&lt;__&gt;", "&lt;.*&gt;", "z") = "z"
+ * StringUtils.replacePattern("ABCabc123", "[a-z]", "_") = "ABC___123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "_") = "ABC_123"
+ * StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "") = "ABC123"
+ * StringUtils.replacePattern("Lorem ipsum dolor sit", "( +)([a-z]+)", "_$2") = "Lorem_ipsum_dolor_sit"
+ * </pre>
+ *
+ * @param source
+ * the source string
+ * @param regex
+ * the regular expression to which this string is to be matched
+ * @param replacement
+ * the string to be substituted for each match
+ * @return The resulting {@link String}
+ * @see #replaceAll(String, String, String)
+ * @see String#replaceAll(String, String)
+ * @see Pattern#DOTALL
+ * @since 3.2
+ * @since 3.5 Changed {@code null} reference passed to this method is a no-op.
+ *
+ * @deprecated Moved to RegExUtils.
+ */
+ @Deprecated
+ public static String replacePattern(final String source, final String regex, final String replacement) {
+ return RegExUtils.replacePattern(source, regex, replacement);
+ }
+
+ /**
+ * Reverses a String as per {@link StringBuilder#reverse()}.
+ *
+ * <p>A {@code null} String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.reverse(null) = null
+ * StringUtils.reverse("") = ""
+ * StringUtils.reverse("bat") = "tab"
+ * </pre>
+ *
+ * @param str the String to reverse, may be null
+ * @return the reversed String, {@code null} if null String input
+ */
+ public static String reverse(final String str) {
+ if (str == null) {
+ return null;
+ }
+ return new StringBuilder(str).reverse().toString();
+ }
+
+ /**
+ * Reverses a String that is delimited by a specific character.
+ *
+ * <p>The Strings between the delimiters are not reversed.
+ * Thus java.lang.String becomes String.lang.java (if the delimiter
+ * is {@code '.'}).</p>
+ *
+ * <pre>
+ * StringUtils.reverseDelimited(null, *) = null
+ * StringUtils.reverseDelimited("", *) = ""
+ * StringUtils.reverseDelimited("a.b.c", 'x') = "a.b.c"
+ * StringUtils.reverseDelimited("a.b.c", ".") = "c.b.a"
+ * </pre>
+ *
+ * @param str the String to reverse, may be null
+ * @param separatorChar the separator character to use
+ * @return the reversed String, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String reverseDelimited(final String str, final char separatorChar) {
+ if (str == null) {
+ return null;
+ }
+ // could implement manually, but simple way is to reuse other,
+ // probably slower, methods.
+ final String[] strs = split(str, separatorChar);
+ ArrayUtils.reverse(strs);
+ return join(strs, separatorChar);
+ }
+
+ /**
+ * Gets the rightmost {@code len} characters of a String.
+ *
+ * <p>If {@code len} characters are not available, or the String
+ * is {@code null}, the String will be returned without an
+ * an exception. An empty String is returned if len is negative.</p>
+ *
+ * <pre>
+ * StringUtils.right(null, *) = null
+ * StringUtils.right(*, -ve) = ""
+ * StringUtils.right("", *) = ""
+ * StringUtils.right("abc", 0) = ""
+ * StringUtils.right("abc", 2) = "bc"
+ * StringUtils.right("abc", 4) = "abc"
+ * </pre>
+ *
+ * @param str the String to get the rightmost characters from, may be null
+ * @param len the length of the required String
+ * @return the rightmost characters, {@code null} if null String input
+ */
+ public static String right(final String str, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(str.length() - len);
+ }
+
+ /**
+ * Right pad a String with spaces (' ').
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.rightPad(null, *) = null
+ * StringUtils.rightPad("", 3) = " "
+ * StringUtils.rightPad("bat", 3) = "bat"
+ * StringUtils.rightPad("bat", 5) = "bat "
+ * StringUtils.rightPad("bat", 1) = "bat"
+ * StringUtils.rightPad("bat", -1) = "bat"
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(final String str, final int size) {
+ return rightPad(str, size, ' ');
+ }
+
+ /**
+ * Right pad a String with a specified character.
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, 'z') = "zzz"
+ * StringUtils.rightPad("bat", 3, 'z') = "bat"
+ * StringUtils.rightPad("bat", 5, 'z') = "batzz"
+ * StringUtils.rightPad("bat", 1, 'z') = "bat"
+ * StringUtils.rightPad("bat", -1, 'z') = "bat"
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padChar the character to pad with
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String rightPad(final String str, final int size, final char padChar) {
+ if (str == null) {
+ return null;
+ }
+ final int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return rightPad(str, size, String.valueOf(padChar));
+ }
+ return str.concat(repeat(padChar, pads));
+ }
+
+ /**
+ * Right pad a String with a specified String.
+ *
+ * <p>The String is padded to the size of {@code size}.</p>
+ *
+ * <pre>
+ * StringUtils.rightPad(null, *, *) = null
+ * StringUtils.rightPad("", 3, "z") = "zzz"
+ * StringUtils.rightPad("bat", 3, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, "yz") = "batyz"
+ * StringUtils.rightPad("bat", 8, "yz") = "batyzyzy"
+ * StringUtils.rightPad("bat", 1, "yz") = "bat"
+ * StringUtils.rightPad("bat", -1, "yz") = "bat"
+ * StringUtils.rightPad("bat", 5, null) = "bat "
+ * StringUtils.rightPad("bat", 5, "") = "bat "
+ * </pre>
+ *
+ * @param str the String to pad out, may be null
+ * @param size the size to pad to
+ * @param padStr the String to pad with, null or empty treated as single space
+ * @return right padded String or original String if no padding is necessary,
+ * {@code null} if null String input
+ */
+ public static String rightPad(final String str, final int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int padLen = padStr.length();
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return rightPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return str.concat(padStr);
+ }
+ if (pads < padLen) {
+ return str.concat(padStr.substring(0, pads));
+ }
+ final char[] padding = new char[pads];
+ final char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return str.concat(new String(padding));
+ }
+
+ /**
+ * Rotate (circular shift) a String of {@code shift} characters.
+ * <ul>
+ * <li>If {@code shift > 0}, right circular shift (ex : ABCDEF =&gt; FABCDE)</li>
+ * <li>If {@code shift < 0}, left circular shift (ex : ABCDEF =&gt; BCDEFA)</li>
+ * </ul>
+ *
+ * <pre>
+ * StringUtils.rotate(null, *) = null
+ * StringUtils.rotate("", *) = ""
+ * StringUtils.rotate("abcdefg", 0) = "abcdefg"
+ * StringUtils.rotate("abcdefg", 2) = "fgabcde"
+ * StringUtils.rotate("abcdefg", -2) = "cdefgab"
+ * StringUtils.rotate("abcdefg", 7) = "abcdefg"
+ * StringUtils.rotate("abcdefg", -7) = "abcdefg"
+ * StringUtils.rotate("abcdefg", 9) = "fgabcde"
+ * StringUtils.rotate("abcdefg", -9) = "cdefgab"
+ * </pre>
+ *
+ * @param str the String to rotate, may be null
+ * @param shift number of time to shift (positive : right shift, negative : left shift)
+ * @return the rotated String,
+ * or the original String if {@code shift == 0},
+ * or {@code null} if null String input
+ * @since 3.5
+ */
+ public static String rotate(final String str, final int shift) {
+ if (str == null) {
+ return null;
+ }
+
+ final int strLen = str.length();
+ if (shift == 0 || strLen == 0 || shift % strLen == 0) {
+ return str;
+ }
+
+ final StringBuilder builder = new StringBuilder(strLen);
+ final int offset = - (shift % strLen);
+ builder.append(substring(str, offset));
+ builder.append(substring(str, 0, offset));
+ return builder.toString();
+ }
+
+ /**
+ * Splits the provided text into an array, using whitespace as the
+ * separator.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.split(null) = null
+ * StringUtils.split("") = []
+ * StringUtils.split("abc def") = ["abc", "def"]
+ * StringUtils.split("abc def") = ["abc", "def"]
+ * StringUtils.split(" abc ") = ["abc"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(final String str) {
+ return split(str, null, -1);
+ }
+
+ /**
+ * Splits the provided text into an array, separator specified.
+ * This is an alternative to using StringTokenizer.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *) = null
+ * StringUtils.split("", *) = []
+ * StringUtils.split("a.b.c", '.') = ["a", "b", "c"]
+ * StringUtils.split("a..b.c", '.') = ["a", "b", "c"]
+ * StringUtils.split("a:b:c", '.') = ["a:b:c"]
+ * StringUtils.split("a b c", ' ') = ["a", "b", "c"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChar the character used as the delimiter
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.0
+ */
+ public static String[] split(final String str, final char separatorChar) {
+ return splitWorker(str, separatorChar, false);
+ }
+
+ /**
+ * Splits the provided text into an array, separators specified.
+ * This is an alternative to using StringTokenizer.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *) = null
+ * StringUtils.split("", *) = []
+ * StringUtils.split("abc def", null) = ["abc", "def"]
+ * StringUtils.split("abc def", " ") = ["abc", "def"]
+ * StringUtils.split("abc def", " ") = ["abc", "def"]
+ * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(final String str, final String separatorChars) {
+ return splitWorker(str, separatorChars, -1, false);
+ }
+
+ /**
+ * Splits the provided text into an array with a maximum length,
+ * separators specified.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <p>If more than {@code max} delimited substrings are found, the last
+ * returned string includes all characters after the first {@code max - 1}
+ * returned strings (including separator characters).</p>
+ *
+ * <pre>
+ * StringUtils.split(null, *, *) = null
+ * StringUtils.split("", *, *) = []
+ * StringUtils.split("ab cd ef", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab cd ef", null, 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab:cd:ef", ":", 0) = ["ab", "cd", "ef"]
+ * StringUtils.split("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ public static String[] split(final String str, final String separatorChars, final int max) {
+ return splitWorker(str, separatorChars, max, false);
+ }
+
+ /**
+ * Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens.
+ * <pre>
+ * StringUtils.splitByCharacterType(null) = null
+ * StringUtils.splitByCharacterType("") = []
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterType("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterType("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterType("fooBar") = ["foo", "B", "ar"]
+ * StringUtils.splitByCharacterType("foo200Bar") = ["foo", "200", "B", "ar"]
+ * StringUtils.splitByCharacterType("ASFRules") = ["ASFR", "ules"]
+ * </pre>
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterType(final String str) {
+ return splitByCharacterType(str, false);
+ }
+
+ /**
+ * <p>Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: if {@code camelCase} is {@code true},
+ * the character of type {@code Character.UPPERCASE_LETTER}, if any,
+ * immediately preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ * @param str the String to split, may be {@code null}
+ * @param camelCase whether to use so-called "camel-case" for letter types
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ private static String[] splitByCharacterType(final String str, final boolean camelCase) {
+ if (str == null) {
+ return null;
+ }
+ if (str.isEmpty()) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ final char[] c = str.toCharArray();
+ final List<String> list = new ArrayList<>();
+ int tokenStart = 0;
+ int currentType = Character.getType(c[tokenStart]);
+ for (int pos = tokenStart + 1; pos < c.length; pos++) {
+ final int type = Character.getType(c[pos]);
+ if (type == currentType) {
+ continue;
+ }
+ if (camelCase && type == Character.LOWERCASE_LETTER && currentType == Character.UPPERCASE_LETTER) {
+ final int newTokenStart = pos - 1;
+ if (newTokenStart != tokenStart) {
+ list.add(new String(c, tokenStart, newTokenStart - tokenStart));
+ tokenStart = newTokenStart;
+ }
+ } else {
+ list.add(new String(c, tokenStart, pos - tokenStart));
+ tokenStart = pos;
+ }
+ currentType = type;
+ }
+ list.add(new String(c, tokenStart, c.length - tokenStart));
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * <p>Splits a String by Character type as returned by
+ * {@code java.lang.Character.getType(char)}. Groups of contiguous
+ * characters of the same type are returned as complete tokens, with the
+ * following exception: the character of type
+ * {@code Character.UPPERCASE_LETTER}, if any, immediately
+ * preceding a token of type {@code Character.LOWERCASE_LETTER}
+ * will belong to the following token rather than to the preceding, if any,
+ * {@code Character.UPPERCASE_LETTER} token.
+ * <pre>
+ * StringUtils.splitByCharacterTypeCamelCase(null) = null
+ * StringUtils.splitByCharacterTypeCamelCase("") = []
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab de fg") = ["ab", " ", "de", " ", "fg"]
+ * StringUtils.splitByCharacterTypeCamelCase("ab:cd:ef") = ["ab", ":", "cd", ":", "ef"]
+ * StringUtils.splitByCharacterTypeCamelCase("number5") = ["number", "5"]
+ * StringUtils.splitByCharacterTypeCamelCase("fooBar") = ["foo", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("foo200Bar") = ["foo", "200", "Bar"]
+ * StringUtils.splitByCharacterTypeCamelCase("ASFRules") = ["ASF", "Rules"]
+ * </pre>
+ * @param str the String to split, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ public static String[] splitByCharacterTypeCamelCase(final String str) {
+ return splitByCharacterType(str, true);
+ }
+
+ /**
+ * <p>Splits the provided text into an array, separator string specified.
+ *
+ * <p>The separator(s) will not be included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparator(null, *) = null
+ * StringUtils.splitByWholeSeparator("", *) = []
+ * StringUtils.splitByWholeSeparator("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String was input
+ */
+ public static String[] splitByWholeSeparator(final String str, final String separator) {
+ return splitByWholeSeparatorWorker(str, separator, -1, false);
+ }
+
+ /**
+ * Splits the provided text into an array, separator string specified.
+ * Returns a maximum of {@code max} substrings.
+ *
+ * <p>The separator(s) will not be included in the returned String array.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparator(null, *, *) = null
+ * StringUtils.splitByWholeSeparator("", *, *) = []
+ * StringUtils.splitByWholeSeparator("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @return an array of parsed Strings, {@code null} if null String was input
+ */
+ public static String[] splitByWholeSeparator( final String str, final String separator, final int max) {
+ return splitByWholeSeparatorWorker(str, separator, max, false);
+ }
+
+ /**
+ * Splits the provided text into an array, separator string specified.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *) = null
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *) = []
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null) = ["ab", "", "", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String was input
+ * @since 2.4
+ */
+ public static String[] splitByWholeSeparatorPreserveAllTokens(final String str, final String separator) {
+ return splitByWholeSeparatorWorker(str, separator, -1, true);
+ }
+
+ /**
+ * Splits the provided text into an array, separator string specified.
+ * Returns a maximum of {@code max} substrings.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separator splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens(null, *, *) = null
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("", *, *) = []
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab de fg", null, 0) = ["ab", "", "", "de", "fg"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
+ * StringUtils.splitByWholeSeparatorPreserveAllTokens("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
+ * </pre>
+ *
+ * @param str the String to parse, may be null
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @return an array of parsed Strings, {@code null} if null String was input
+ * @since 2.4
+ */
+ public static String[] splitByWholeSeparatorPreserveAllTokens(final String str, final String separator, final int max) {
+ return splitByWholeSeparatorWorker(str, separator, max, true);
+ }
+
+ /**
+ * Performs the logic for the {@code splitByWholeSeparatorPreserveAllTokens} methods.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separator String containing the String to be used as a delimiter,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the returned
+ * array. A zero or negative value implies no limit.
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.4
+ */
+ private static String[] splitByWholeSeparatorWorker(
+ final String str, final String separator, final int max, final boolean preserveAllTokens) {
+ if (str == null) {
+ return null;
+ }
+
+ final int len = str.length();
+
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+
+ if (separator == null || EMPTY.equals(separator)) {
+ // Split on whitespace.
+ return splitWorker(str, null, max, preserveAllTokens);
+ }
+
+ final int separatorLength = separator.length();
+
+ final ArrayList<String> substrings = new ArrayList<>();
+ int numberOfSubstrings = 0;
+ int beg = 0;
+ int end = 0;
+ while (end < len) {
+ end = str.indexOf(separator, beg);
+
+ if (end > -1) {
+ if (end > beg) {
+ numberOfSubstrings += 1;
+
+ if (numberOfSubstrings == max) {
+ end = len;
+ substrings.add(str.substring(beg));
+ } else {
+ // The following is OK, because String.substring( beg, end ) excludes
+ // the character at the position 'end'.
+ substrings.add(str.substring(beg, end));
+
+ // Set the starting point for the next search.
+ // The following is equivalent to beg = end + (separatorLength - 1) + 1,
+ // which is the right calculation:
+ beg = end + separatorLength;
+ }
+ } else {
+ // We found a consecutive occurrence of the separator, so skip it.
+ if (preserveAllTokens) {
+ numberOfSubstrings += 1;
+ if (numberOfSubstrings == max) {
+ end = len;
+ substrings.add(str.substring(beg));
+ } else {
+ substrings.add(EMPTY);
+ }
+ }
+ beg = end + separatorLength;
+ }
+ } else {
+ // String.substring( beg ) goes from 'beg' to the end of the String.
+ substrings.add(str.substring(beg));
+ end = len;
+ }
+ }
+
+ return substrings.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Splits the provided text into an array, using whitespace as the
+ * separator, preserving all tokens, including empty tokens created by
+ * adjacent separators. This is an alternative to using StringTokenizer.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null) = null
+ * StringUtils.splitPreserveAllTokens("") = []
+ * StringUtils.splitPreserveAllTokens("abc def") = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def") = ["abc", "", "def"]
+ * StringUtils.splitPreserveAllTokens(" abc ") = ["", "abc", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(final String str) {
+ return splitWorker(str, null, -1, true);
+ }
+
+ /**
+ * Splits the provided text into an array, separator specified,
+ * preserving all tokens, including empty tokens created by adjacent
+ * separators. This is an alternative to using StringTokenizer.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *) = null
+ * StringUtils.splitPreserveAllTokens("", *) = []
+ * StringUtils.splitPreserveAllTokens("a.b.c", '.') = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a..b.c", '.') = ["a", "", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a:b:c", '.') = ["a:b:c"]
+ * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a b c", ' ') = ["a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens("a b c ", ' ') = ["a", "b", "c", ""]
+ * StringUtils.splitPreserveAllTokens("a b c ", ' ') = ["a", "b", "c", "", ""]
+ * StringUtils.splitPreserveAllTokens(" a b c", ' ') = ["", a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens(" a b c", ' ') = ["", "", a", "b", "c"]
+ * StringUtils.splitPreserveAllTokens(" a b c ", ' ') = ["", a", "b", "c", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChar the character used as the delimiter,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(final String str, final char separatorChar) {
+ return splitWorker(str, separatorChar, true);
+ }
+
+ /**
+ * Splits the provided text into an array, separators specified,
+ * preserving all tokens, including empty tokens created by adjacent
+ * separators. This is an alternative to using StringTokenizer.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * For more control over the split use the StrTokenizer class.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *) = null
+ * StringUtils.splitPreserveAllTokens("", *) = []
+ * StringUtils.splitPreserveAllTokens("abc def", null) = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "def"]
+ * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "", def"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":") = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":") = ["ab", "cd", "ef", ""]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
+ * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":") = ["ab", "", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens(":cd:ef", ":") = ["", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("::cd:ef", ":") = ["", "", cd", "ef"]
+ * StringUtils.splitPreserveAllTokens(":cd:ef:", ":") = ["", cd", "ef", ""]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(final String str, final String separatorChars) {
+ return splitWorker(str, separatorChars, -1, true);
+ }
+
+ /**
+ * Splits the provided text into an array with a maximum length,
+ * separators specified, preserving all tokens, including empty tokens
+ * created by adjacent separators.
+ *
+ * <p>The separator is not included in the returned String array.
+ * Adjacent separators are treated as separators for empty tokens.
+ * Adjacent separators are treated as one separator.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} separatorChars splits on whitespace.</p>
+ *
+ * <p>If more than {@code max} delimited substrings are found, the last
+ * returned string includes all characters after the first {@code max - 1}
+ * returned strings (including separator characters).</p>
+ *
+ * <pre>
+ * StringUtils.splitPreserveAllTokens(null, *, *) = null
+ * StringUtils.splitPreserveAllTokens("", *, *) = []
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 0) = ["ab", "de", "fg"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 0) = ["ab", "", "", "de", "fg"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0) = ["ab", "cd", "ef"]
+ * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2) = ["ab", "cd:ef"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 2) = ["ab", " de fg"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 3) = ["ab", "", " de fg"]
+ * StringUtils.splitPreserveAllTokens("ab de fg", null, 4) = ["ab", "", "", "de fg"]
+ * </pre>
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the characters used as the delimiters,
+ * {@code null} splits on whitespace
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit
+ * @return an array of parsed Strings, {@code null} if null String input
+ * @since 2.1
+ */
+ public static String[] splitPreserveAllTokens(final String str, final String separatorChars, final int max) {
+ return splitWorker(str, separatorChars, max, true);
+ }
+
+ /**
+ * Performs the logic for the {@code split} and
+ * {@code splitPreserveAllTokens} methods that do not return a
+ * maximum array length.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChar the separate character
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ private static String[] splitWorker(final String str, final char separatorChar, final boolean preserveAllTokens) {
+ // Performance tuned for 2.0 (JDK1.4)
+
+ if (str == null) {
+ return null;
+ }
+ final int len = str.length();
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ final List<String> list = new ArrayList<>();
+ int i = 0;
+ int start = 0;
+ boolean match = false;
+ boolean lastMatch = false;
+ while (i < len) {
+ if (str.charAt(i) == separatorChar) {
+ if (match || preserveAllTokens) {
+ list.add(str.substring(start, i));
+ match = false;
+ lastMatch = true;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ if (match || preserveAllTokens && lastMatch) {
+ list.add(str.substring(start, i));
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Performs the logic for the {@code split} and
+ * {@code splitPreserveAllTokens} methods that return a maximum array
+ * length.
+ *
+ * @param str the String to parse, may be {@code null}
+ * @param separatorChars the separate character
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit.
+ * @param preserveAllTokens if {@code true}, adjacent separators are
+ * treated as empty token separators; if {@code false}, adjacent
+ * separators are treated as one separator.
+ * @return an array of parsed Strings, {@code null} if null String input
+ */
+ private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) {
+ // Performance tuned for 2.0 (JDK1.4)
+ // Direct code is quicker than StringTokenizer.
+ // Also, StringTokenizer uses isSpace() not isWhitespace()
+
+ if (str == null) {
+ return null;
+ }
+ final int len = str.length();
+ if (len == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ final List<String> list = new ArrayList<>();
+ int sizePlus1 = 1;
+ int i = 0;
+ int start = 0;
+ boolean match = false;
+ boolean lastMatch = false;
+ if (separatorChars == null) {
+ // Null separator means use whitespace
+ while (i < len) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else if (separatorChars.length() == 1) {
+ // Optimise 1 character case
+ final char sep = separatorChars.charAt(0);
+ while (i < len) {
+ if (str.charAt(i) == sep) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ } else {
+ // standard case
+ while (i < len) {
+ if (separatorChars.indexOf(str.charAt(i)) >= 0) {
+ if (match || preserveAllTokens) {
+ lastMatch = true;
+ if (sizePlus1++ == max) {
+ i = len;
+ lastMatch = false;
+ }
+ list.add(str.substring(start, i));
+ match = false;
+ }
+ start = ++i;
+ continue;
+ }
+ lastMatch = false;
+ match = true;
+ i++;
+ }
+ }
+ if (match || preserveAllTokens && lastMatch) {
+ list.add(str.substring(start, i));
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Check if a CharSequence starts with a specified prefix.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case-sensitive.</p>
+ *
+ * <pre>
+ * StringUtils.startsWith(null, null) = true
+ * StringUtils.startsWith(null, "abc") = false
+ * StringUtils.startsWith("abcdef", null) = false
+ * StringUtils.startsWith("abcdef", "abc") = true
+ * StringUtils.startsWith("ABCDEF", "abc") = false
+ * </pre>
+ *
+ * @see String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case-sensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence)
+ */
+ public static boolean startsWith(final CharSequence str, final CharSequence prefix) {
+ return startsWith(str, prefix, false);
+ }
+
+ /**
+ * Check if a CharSequence starts with a specified prefix (optionally case insensitive).
+ *
+ * @see String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @param ignoreCase indicates whether the compare should ignore case
+ * (case-insensitive) or not.
+ * @return {@code true} if the CharSequence starts with the prefix or
+ * both {@code null}
+ */
+ private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) {
+ if (str == null || prefix == null) {
+ return str == prefix;
+ }
+ // Get length once instead of twice in the unlikely case that it changes.
+ final int preLen = prefix.length();
+ if (preLen > str.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen);
+ }
+
+ /**
+ * Check if a CharSequence starts with any of the provided case-sensitive prefixes.
+ *
+ * <pre>
+ * StringUtils.startsWithAny(null, null) = false
+ * StringUtils.startsWithAny(null, new String[] {"abc"}) = false
+ * StringUtils.startsWithAny("abcxyz", null) = false
+ * StringUtils.startsWithAny("abcxyz", new String[] {""}) = true
+ * StringUtils.startsWithAny("abcxyz", new String[] {"abc"}) = true
+ * StringUtils.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
+ * StringUtils.startsWithAny("abcxyz", null, "xyz", "ABCX") = false
+ * StringUtils.startsWithAny("ABCXYZ", null, "xyz", "abc") = false
+ * </pre>
+ *
+ * @param sequence the CharSequence to check, may be null
+ * @param searchStrings the case-sensitive CharSequence prefixes, may be empty or contain {@code null}
+ * @see StringUtils#startsWith(CharSequence, CharSequence)
+ * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or
+ * the input {@code sequence} begins with any of the provided case-sensitive {@code searchStrings}.
+ * @since 2.5
+ * @since 3.0 Changed signature from startsWithAny(String, String[]) to startsWithAny(CharSequence, CharSequence...)
+ */
+ public static boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
+ if (isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) {
+ return false;
+ }
+ for (final CharSequence searchString : searchStrings) {
+ if (startsWith(sequence, searchString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Case insensitive check if a CharSequence starts with a specified prefix.
+ *
+ * <p>{@code null}s are handled without exceptions. Two {@code null}
+ * references are considered to be equal. The comparison is case insensitive.</p>
+ *
+ * <pre>
+ * StringUtils.startsWithIgnoreCase(null, null) = true
+ * StringUtils.startsWithIgnoreCase(null, "abc") = false
+ * StringUtils.startsWithIgnoreCase("abcdef", null) = false
+ * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
+ * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
+ * </pre>
+ *
+ * @see String#startsWith(String)
+ * @param str the CharSequence to check, may be null
+ * @param prefix the prefix to find, may be null
+ * @return {@code true} if the CharSequence starts with the prefix, case-insensitive, or
+ * both {@code null}
+ * @since 2.4
+ * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence)
+ */
+ public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) {
+ return startsWith(str, prefix, true);
+ }
+
+ /**
+ * Strips whitespace from the start and end of a String.
+ *
+ * <p>This is similar to {@link #trim(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.strip(null) = null
+ * StringUtils.strip("") = ""
+ * StringUtils.strip(" ") = ""
+ * StringUtils.strip("abc") = "abc"
+ * StringUtils.strip(" abc") = "abc"
+ * StringUtils.strip("abc ") = "abc"
+ * StringUtils.strip(" abc ") = "abc"
+ * StringUtils.strip(" ab c ") = "ab c"
+ * </pre>
+ *
+ * @param str the String to remove whitespace from, may be null
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String strip(final String str) {
+ return strip(str, null);
+ }
+
+ /**
+ * Strips any of a set of characters from the start and end of a String.
+ * This is similar to {@link String#trim()} but allows the characters
+ * to be stripped to be controlled.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * Alternatively use {@link #strip(String)}.</p>
+ *
+ * <pre>
+ * StringUtils.strip(null, *) = null
+ * StringUtils.strip("", *) = ""
+ * StringUtils.strip("abc", null) = "abc"
+ * StringUtils.strip(" abc", null) = "abc"
+ * StringUtils.strip("abc ", null) = "abc"
+ * StringUtils.strip(" abc ", null) = "abc"
+ * StringUtils.strip(" abcyx", "xyz") = " abc"
+ * </pre>
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String strip(String str, final String stripChars) {
+ str = stripStart(str, stripChars);
+ return stripEnd(str, stripChars);
+ }
+
+ /**
+ * Removes diacritics (~= accents) from a string. The case will not be altered.
+ * <p>For instance, '&agrave;' will be replaced by 'a'.</p>
+ * <p>Note that ligatures will be left as is.</p>
+ *
+ * <pre>
+ * StringUtils.stripAccents(null) = null
+ * StringUtils.stripAccents("") = ""
+ * StringUtils.stripAccents("control") = "control"
+ * StringUtils.stripAccents("&eacute;clair") = "eclair"
+ * </pre>
+ *
+ * @param input String to be stripped
+ * @return input text with diacritics removed
+ *
+ * @since 3.0
+ */
+ // See also Lucene's ASCIIFoldingFilter (Lucene 2.9) that replaces accented characters by their unaccented equivalent (and uncommitted bug fix: https://issues.apache.org/jira/browse/LUCENE-1343?focusedCommentId=12858907&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12858907).
+ public static String stripAccents(final String input) {
+ if (input == null) {
+ return null;
+ }
+ final StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD));
+ convertRemainingAccentCharacters(decomposed);
+ // Note that this doesn't correctly remove ligatures...
+ return STRIP_ACCENTS_PATTERN.matcher(decomposed).replaceAll(EMPTY);
+ }
+
+ /**
+ * Strips whitespace from the start and end of every String in an array.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.
+ *
+ * <p>A new array is returned each time, except for length zero.
+ * A {@code null} array will return {@code null}.
+ * An empty array will return itself.
+ * A {@code null} array entry will be ignored.</p>
+ *
+ * <pre>
+ * StringUtils.stripAll(null) = null
+ * StringUtils.stripAll([]) = []
+ * StringUtils.stripAll(["abc", " abc"]) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null]) = ["abc", null]
+ * </pre>
+ *
+ * @param strs the array to remove whitespace from, may be null
+ * @return the stripped Strings, {@code null} if null array input
+ */
+ public static String[] stripAll(final String... strs) {
+ return stripAll(strs, null);
+ }
+
+ /**
+ * Strips any of a set of characters from the start and end of every
+ * String in an array.
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <p>A new array is returned each time, except for length zero.
+ * A {@code null} array will return {@code null}.
+ * An empty array will return itself.
+ * A {@code null} array entry will be ignored.
+ * A {@code null} stripChars will strip whitespace as defined by
+ * {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.stripAll(null, *) = null
+ * StringUtils.stripAll([], *) = []
+ * StringUtils.stripAll(["abc", " abc"], null) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null], null) = ["abc", null]
+ * StringUtils.stripAll(["abc ", null], "yz") = ["abc ", null]
+ * StringUtils.stripAll(["yabcz", null], "yz") = ["abc", null]
+ * </pre>
+ *
+ * @param strs the array to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped Strings, {@code null} if null array input
+ */
+ public static String[] stripAll(final String[] strs, final String stripChars) {
+ final int strsLen = ArrayUtils.getLength(strs);
+ if (strsLen == 0) {
+ return strs;
+ }
+ final String[] newArr = new String[strsLen];
+ Arrays.setAll(newArr, i -> strip(strs[i], stripChars));
+ return newArr;
+ }
+
+ /**
+ * Strips any of a set of characters from the end of a String.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.stripEnd(null, *) = null
+ * StringUtils.stripEnd("", *) = ""
+ * StringUtils.stripEnd("abc", "") = "abc"
+ * StringUtils.stripEnd("abc", null) = "abc"
+ * StringUtils.stripEnd(" abc", null) = " abc"
+ * StringUtils.stripEnd("abc ", null) = "abc"
+ * StringUtils.stripEnd(" abc ", null) = " abc"
+ * StringUtils.stripEnd(" abcyx", "xyz") = " abc"
+ * StringUtils.stripEnd("120.00", ".0") = "12"
+ * </pre>
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the set of characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String stripEnd(final String str, final String stripChars) {
+ int end = length(str);
+ if (end == 0) {
+ return str;
+ }
+
+ if (stripChars == null) {
+ while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
+ end--;
+ }
+ } else if (stripChars.isEmpty()) {
+ return str;
+ } else {
+ while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) {
+ end--;
+ }
+ }
+ return str.substring(0, end);
+ }
+
+ /**
+ * Strips any of a set of characters from the start of a String.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * An empty string ("") input returns the empty string.</p>
+ *
+ * <p>If the stripChars String is {@code null}, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.stripStart(null, *) = null
+ * StringUtils.stripStart("", *) = ""
+ * StringUtils.stripStart("abc", "") = "abc"
+ * StringUtils.stripStart("abc", null) = "abc"
+ * StringUtils.stripStart(" abc", null) = "abc"
+ * StringUtils.stripStart("abc ", null) = "abc "
+ * StringUtils.stripStart(" abc ", null) = "abc "
+ * StringUtils.stripStart("yxabc ", "xyz") = "abc "
+ * </pre>
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, {@code null} if null String input
+ */
+ public static String stripStart(final String str, final String stripChars) {
+ final int strLen = length(str);
+ if (strLen == 0) {
+ return str;
+ }
+ int start = 0;
+ if (stripChars == null) {
+ while (start != strLen && Character.isWhitespace(str.charAt(start))) {
+ start++;
+ }
+ } else if (stripChars.isEmpty()) {
+ return str;
+ } else {
+ while (start != strLen && stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND) {
+ start++;
+ }
+ }
+ return str.substring(start);
+ }
+
+ /**
+ * Strips whitespace from the start and end of a String returning
+ * an empty String if {@code null} input.
+ *
+ * <p>This is similar to {@link #trimToEmpty(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.stripToEmpty(null) = ""
+ * StringUtils.stripToEmpty("") = ""
+ * StringUtils.stripToEmpty(" ") = ""
+ * StringUtils.stripToEmpty("abc") = "abc"
+ * StringUtils.stripToEmpty(" abc") = "abc"
+ * StringUtils.stripToEmpty("abc ") = "abc"
+ * StringUtils.stripToEmpty(" abc ") = "abc"
+ * StringUtils.stripToEmpty(" ab c ") = "ab c"
+ * </pre>
+ *
+ * @param str the String to be stripped, may be null
+ * @return the trimmed String, or an empty String if {@code null} input
+ * @since 2.0
+ */
+ public static String stripToEmpty(final String str) {
+ return str == null ? EMPTY : strip(str, null);
+ }
+
+ /**
+ * Strips whitespace from the start and end of a String returning
+ * {@code null} if the String is empty ("") after the strip.
+ *
+ * <p>This is similar to {@link #trimToNull(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
+ *
+ * <pre>
+ * StringUtils.stripToNull(null) = null
+ * StringUtils.stripToNull("") = null
+ * StringUtils.stripToNull(" ") = null
+ * StringUtils.stripToNull("abc") = "abc"
+ * StringUtils.stripToNull(" abc") = "abc"
+ * StringUtils.stripToNull("abc ") = "abc"
+ * StringUtils.stripToNull(" abc ") = "abc"
+ * StringUtils.stripToNull(" ab c ") = "ab c"
+ * </pre>
+ *
+ * @param str the String to be stripped, may be null
+ * @return the stripped String,
+ * {@code null} if whitespace, empty or null String input
+ * @since 2.0
+ */
+ public static String stripToNull(String str) {
+ if (str == null) {
+ return null;
+ }
+ str = strip(str, null);
+ return str.isEmpty() ? null : str; // NOSONARLINT str cannot be null here
+ }
+
+ /**
+ * Gets a substring from the specified String avoiding exceptions.
+ *
+ * <p>A negative start position can be used to start {@code n}
+ * characters from the end of the String.</p>
+ *
+ * <p>A {@code null} String will return {@code null}.
+ * An empty ("") String will return "".</p>
+ *
+ * <pre>
+ * StringUtils.substring(null, *) = null
+ * StringUtils.substring("", *) = ""
+ * StringUtils.substring("abc", 0) = "abc"
+ * StringUtils.substring("abc", 2) = "c"
+ * StringUtils.substring("abc", 4) = ""
+ * StringUtils.substring("abc", -2) = "bc"
+ * StringUtils.substring("abc", -4) = "abc"
+ * </pre>
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position, {@code null} if null String input
+ */
+ public static String substring(final String str, int start) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives, which means last n characters
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > str.length()) {
+ return EMPTY;
+ }
+
+ return str.substring(start);
+ }
+
+ /**
+ * Gets a substring from the specified String avoiding exceptions.
+ *
+ * <p>A negative start position can be used to start/end {@code n}
+ * characters from the end of the String.</p>
+ *
+ * <p>The returned substring starts with the character in the {@code start}
+ * position and ends before the {@code end} position. All position counting is
+ * zero-based -- i.e., to start at the beginning of the string use
+ * {@code start = 0}. Negative start and end positions can be used to
+ * specify offsets relative to the end of the String.</p>
+ *
+ * <p>If {@code start} is not strictly to the left of {@code end}, ""
+ * is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substring(null, *, *) = null
+ * StringUtils.substring("", * , *) = "";
+ * StringUtils.substring("abc", 0, 2) = "ab"
+ * StringUtils.substring("abc", 2, 0) = ""
+ * StringUtils.substring("abc", 2, 4) = "c"
+ * StringUtils.substring("abc", 4, 6) = ""
+ * StringUtils.substring("abc", 2, 2) = ""
+ * StringUtils.substring("abc", -2, -1) = "b"
+ * StringUtils.substring("abc", -4, 2) = "ab"
+ * </pre>
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @param end the position to end at (exclusive), negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position to end position,
+ * {@code null} if null String input
+ */
+ public static String substring(final String str, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives
+ if (end < 0) {
+ end = str.length() + end; // remember end is negative
+ }
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ // check length next
+ if (end > str.length()) {
+ end = str.length();
+ }
+
+ // if start is greater than end, return ""
+ if (start > end) {
+ return EMPTY;
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+
+ return str.substring(start, end);
+ }
+
+ /**
+ * Gets the substring after the first occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringAfter(null, *) = null
+ * StringUtils.substringAfter("", *) = ""
+ * StringUtils.substringAfter("abc", 'a') = "bc"
+ * StringUtils.substringAfter("abcba", 'b') = "cba"
+ * StringUtils.substringAfter("abc", 'c') = ""
+ * StringUtils.substringAfter("abc", 'd') = ""
+ * StringUtils.substringAfter(" abc", 32) = "abc"
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the character (Unicode code point) to search.
+ * @return the substring after the first occurrence of the separator,
+ * {@code null} if null String input
+ * @since 3.11
+ */
+ public static String substringAfter(final String str, final int separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str.substring(pos + 1);
+ }
+
+ /**
+ * Gets the substring after the first occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * A {@code null} separator will return the empty string if the
+ * input string is not {@code null}.</p>
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringAfter(null, *) = null
+ * StringUtils.substringAfter("", *) = ""
+ * StringUtils.substringAfter(*, null) = ""
+ * StringUtils.substringAfter("abc", "a") = "bc"
+ * StringUtils.substringAfter("abcba", "b") = "cba"
+ * StringUtils.substringAfter("abc", "c") = ""
+ * StringUtils.substringAfter("abc", "d") = ""
+ * StringUtils.substringAfter("abc", "") = "abc"
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the first occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringAfter(final String str, final String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (separator == null) {
+ return EMPTY;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * Gets the substring after the last occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringAfterLast(null, *) = null
+ * StringUtils.substringAfterLast("", *) = ""
+ * StringUtils.substringAfterLast("abc", 'a') = "bc"
+ * StringUtils.substringAfterLast(" bc", 32) = "bc"
+ * StringUtils.substringAfterLast("abcba", 'b') = "a"
+ * StringUtils.substringAfterLast("abc", 'c') = ""
+ * StringUtils.substringAfterLast("a", 'a') = ""
+ * StringUtils.substringAfterLast("a", 'z') = ""
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the character (Unicode code point) to search.
+ * @return the substring after the last occurrence of the separator,
+ * {@code null} if null String input
+ * @since 3.11
+ */
+ public static String substringAfterLast(final String str, final int separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND || pos == str.length() - 1) {
+ return EMPTY;
+ }
+ return str.substring(pos + 1);
+ }
+
+ /**
+ * Gets the substring after the last occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * An empty or {@code null} separator will return the empty string if
+ * the input string is not {@code null}.</p>
+ *
+ * <p>If nothing is found, the empty string is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringAfterLast(null, *) = null
+ * StringUtils.substringAfterLast("", *) = ""
+ * StringUtils.substringAfterLast(*, "") = ""
+ * StringUtils.substringAfterLast(*, null) = ""
+ * StringUtils.substringAfterLast("abc", "a") = "bc"
+ * StringUtils.substringAfterLast("abcba", "b") = "a"
+ * StringUtils.substringAfterLast("abc", "c") = ""
+ * StringUtils.substringAfterLast("a", "a") = ""
+ * StringUtils.substringAfterLast("a", "z") = ""
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the last occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringAfterLast(final String str, final String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (isEmpty(separator)) {
+ return EMPTY;
+ }
+ final int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * Gets the substring before the first occurrence of a separator. The separator is not returned.
+ *
+ * <p>
+ * A {@code null} string input will return {@code null}. An empty ("") string input will return the empty string.
+ * </p>
+ *
+ * <p>
+ * If nothing is found, the string input is returned.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.substringBefore(null, *) = null
+ * StringUtils.substringBefore("", *) = ""
+ * StringUtils.substringBefore("abc", 'a') = ""
+ * StringUtils.substringBefore("abcba", 'b') = "a"
+ * StringUtils.substringBefore("abc", 'c') = "ab"
+ * StringUtils.substringBefore("abc", 'd') = "abc"
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the character (Unicode code point) to search.
+ * @return the substring before the first occurrence of the separator, {@code null} if null String input
+ * @since 3.12.0
+ */
+ public static String substringBefore(final String str, final int separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * Gets the substring before the first occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * A {@code null} separator will return the input string.</p>
+ *
+ * <p>If nothing is found, the string input is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringBefore(null, *) = null
+ * StringUtils.substringBefore("", *) = ""
+ * StringUtils.substringBefore("abc", "a") = ""
+ * StringUtils.substringBefore("abcba", "b") = "a"
+ * StringUtils.substringBefore("abc", "c") = "ab"
+ * StringUtils.substringBefore("abc", "d") = "abc"
+ * StringUtils.substringBefore("abc", "") = ""
+ * StringUtils.substringBefore("abc", null) = "abc"
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the first occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringBefore(final String str, final String separator) {
+ if (isEmpty(str) || separator == null) {
+ return str;
+ }
+ if (separator.isEmpty()) {
+ return EMPTY;
+ }
+ final int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * Gets the substring before the last occurrence of a separator.
+ * The separator is not returned.
+ *
+ * <p>A {@code null} string input will return {@code null}.
+ * An empty ("") string input will return the empty string.
+ * An empty or {@code null} separator will return the input string.</p>
+ *
+ * <p>If nothing is found, the string input is returned.</p>
+ *
+ * <pre>
+ * StringUtils.substringBeforeLast(null, *) = null
+ * StringUtils.substringBeforeLast("", *) = ""
+ * StringUtils.substringBeforeLast("abcba", "b") = "abc"
+ * StringUtils.substringBeforeLast("abc", "c") = "ab"
+ * StringUtils.substringBeforeLast("a", "a") = ""
+ * StringUtils.substringBeforeLast("a", "z") = "a"
+ * StringUtils.substringBeforeLast("a", null) = "a"
+ * StringUtils.substringBeforeLast("a", "") = "a"
+ * </pre>
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the last occurrence of the separator,
+ * {@code null} if null String input
+ * @since 2.0
+ */
+ public static String substringBeforeLast(final String str, final String separator) {
+ if (isEmpty(str) || isEmpty(separator)) {
+ return str;
+ }
+ final int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * Gets the String that is nested in between two instances of the
+ * same String.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} tag returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.substringBetween(null, *) = null
+ * StringUtils.substringBetween("", "") = ""
+ * StringUtils.substringBetween("", "tag") = null
+ * StringUtils.substringBetween("tagabctag", null) = null
+ * StringUtils.substringBetween("tagabctag", "") = ""
+ * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+ * </pre>
+ *
+ * @param str the String containing the substring, may be null
+ * @param tag the String before and after the substring, may be null
+ * @return the substring, {@code null} if no match
+ * @since 2.0
+ */
+ public static String substringBetween(final String str, final String tag) {
+ return substringBetween(str, tag, tag);
+ }
+
+ /**
+ * Gets the String that is nested in between two Strings.
+ * Only the first match is returned.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} open/close returns {@code null} (no match).
+ * An empty ("") open and close returns an empty string.</p>
+ *
+ * <pre>
+ * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
+ * StringUtils.substringBetween(null, *, *) = null
+ * StringUtils.substringBetween(*, null, *) = null
+ * StringUtils.substringBetween(*, *, null) = null
+ * StringUtils.substringBetween("", "", "") = ""
+ * StringUtils.substringBetween("", "", "]") = null
+ * StringUtils.substringBetween("", "[", "]") = null
+ * StringUtils.substringBetween("yabcz", "", "") = ""
+ * StringUtils.substringBetween("yabcz", "y", "z") = "abc"
+ * StringUtils.substringBetween("yabczyabcz", "y", "z") = "abc"
+ * </pre>
+ *
+ * @param str the String containing the substring, may be null
+ * @param open the String before the substring, may be null
+ * @param close the String after the substring, may be null
+ * @return the substring, {@code null} if no match
+ * @since 2.0
+ */
+ public static String substringBetween(final String str, final String open, final String close) {
+ if (!ObjectUtils.allNotNull(str, open, close)) {
+ return null;
+ }
+ final int start = str.indexOf(open);
+ if (start != INDEX_NOT_FOUND) {
+ final int end = str.indexOf(close, start + open.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str.substring(start + open.length(), end);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Searches a String for substrings delimited by a start and end tag,
+ * returning all matching substrings in an array.
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * A {@code null} open/close returns {@code null} (no match).
+ * An empty ("") open/close returns {@code null} (no match).</p>
+ *
+ * <pre>
+ * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
+ * StringUtils.substringsBetween(null, *, *) = null
+ * StringUtils.substringsBetween(*, null, *) = null
+ * StringUtils.substringsBetween(*, *, null) = null
+ * StringUtils.substringsBetween("", "[", "]") = []
+ * </pre>
+ *
+ * @param str the String containing the substrings, null returns null, empty returns empty
+ * @param open the String identifying the start of the substring, empty returns null
+ * @param close the String identifying the end of the substring, empty returns null
+ * @return a String Array of substrings, or {@code null} if no match
+ * @since 2.3
+ */
+ public static String[] substringsBetween(final String str, final String open, final String close) {
+ if (str == null || isEmpty(open) || isEmpty(close)) {
+ return null;
+ }
+ final int strLen = str.length();
+ if (strLen == 0) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ final int closeLen = close.length();
+ final int openLen = open.length();
+ final List<String> list = new ArrayList<>();
+ int pos = 0;
+ while (pos < strLen - closeLen) {
+ int start = str.indexOf(open, pos);
+ if (start < 0) {
+ break;
+ }
+ start += openLen;
+ final int end = str.indexOf(close, start);
+ if (end < 0) {
+ break;
+ }
+ list.add(str.substring(start, end));
+ pos = end + closeLen;
+ }
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Swaps the case of a String changing upper and title case to
+ * lower case, and lower case to upper case.
+ *
+ * <ul>
+ * <li>Upper case character converts to Lower case</li>
+ * <li>Title case character converts to Lower case</li>
+ * <li>Lower case character converts to Upper case</li>
+ * </ul>
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.text.WordUtils#swapCase(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ * </pre>
+ *
+ * <p>NOTE: This method changed in Lang version 2.0.
+ * It no longer performs a word based algorithm.
+ * If you only use ASCII, you will notice no change.
+ * That functionality is available in org.apache.commons.lang3.text.WordUtils.</p>
+ *
+ * @param str the String to swap case, may be null
+ * @return the changed String, {@code null} if null String input
+ */
+ public static String swapCase(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+
+ final int strLen = str.length();
+ final int[] newCodePoints = new int[strLen]; // cannot be longer than the char array
+ int outOffset = 0;
+ for (int i = 0; i < strLen; ) {
+ final int oldCodepoint = str.codePointAt(i);
+ final int newCodePoint;
+ if (Character.isUpperCase(oldCodepoint) || Character.isTitleCase(oldCodepoint)) {
+ newCodePoint = Character.toLowerCase(oldCodepoint);
+ } else if (Character.isLowerCase(oldCodepoint)) {
+ newCodePoint = Character.toUpperCase(oldCodepoint);
+ } else {
+ newCodePoint = oldCodepoint;
+ }
+ newCodePoints[outOffset++] = newCodePoint;
+ i += Character.charCount(newCodePoint);
+ }
+ return new String(newCodePoints, 0, outOffset);
+ }
+
+ /**
+ * Converts a {@link CharSequence} into an array of code points.
+ *
+ * <p>Valid pairs of surrogate code units will be converted into a single supplementary
+ * code point. Isolated surrogate code units (i.e. a high surrogate not followed by a low surrogate or
+ * a low surrogate not preceded by a high surrogate) will be returned as-is.</p>
+ *
+ * <pre>
+ * StringUtils.toCodePoints(null) = null
+ * StringUtils.toCodePoints("") = [] // empty array
+ * </pre>
+ *
+ * @param cs the character sequence to convert
+ * @return an array of code points
+ * @since 3.6
+ */
+ public static int[] toCodePoints(final CharSequence cs) {
+ if (cs == null) {
+ return null;
+ }
+ if (cs.length() == 0) {
+ return ArrayUtils.EMPTY_INT_ARRAY;
+ }
+
+ final String s = cs.toString();
+ final int[] result = new int[s.codePointCount(0, s.length())];
+ int index = 0;
+ for (int i = 0; i < result.length; i++) {
+ result[i] = s.codePointAt(index);
+ index += Character.charCount(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Converts a {@code byte[]} to a String using the specified character encoding.
+ *
+ * @param bytes
+ * the byte array to read from
+ * @param charset
+ * the encoding to use, if null then use the platform default
+ * @return a new String
+ * @throws NullPointerException
+ * if {@code bytes} is null
+ * @since 3.2
+ * @since 3.3 No longer throws {@link UnsupportedEncodingException}.
+ */
+ public static String toEncodedString(final byte[] bytes, final Charset charset) {
+ return new String(bytes, Charsets.toCharset(charset));
+ }
+
+ /**
+ * Converts the given source String as a lower-case using the {@link Locale#ROOT} locale in a null-safe manner.
+ *
+ * @param source A source String or null.
+ * @return the given source String as a lower-case using the {@link Locale#ROOT} locale or null.
+ * @since 3.10
+ */
+ public static String toRootLowerCase(final String source) {
+ return source == null ? null : source.toLowerCase(Locale.ROOT);
+ }
+
+ /**
+ * Converts the given source String as a upper-case using the {@link Locale#ROOT} locale in a null-safe manner.
+ *
+ * @param source A source String or null.
+ * @return the given source String as a upper-case using the {@link Locale#ROOT} locale or null.
+ * @since 3.10
+ */
+ public static String toRootUpperCase(final String source) {
+ return source == null ? null : source.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Converts a {@code byte[]} to a String using the specified character encoding.
+ *
+ * @param bytes
+ * the byte array to read from
+ * @param charsetName
+ * the encoding to use, if null then use the platform default
+ * @return a new String
+ * @throws UnsupportedEncodingException
+ * If the named charset is not supported
+ * @throws NullPointerException
+ * if the input is null
+ * @deprecated use {@link StringUtils#toEncodedString(byte[], Charset)} instead of String constants in your code
+ * @since 3.1
+ */
+ @Deprecated
+ public static String toString(final byte[] bytes, final String charsetName) throws UnsupportedEncodingException {
+ return new String(bytes, Charsets.toCharset(charsetName));
+ }
+
+ private static String toStringOrEmpty(final Object obj) {
+ return Objects.toString(obj, EMPTY);
+ }
+
+ /**
+ * Removes control characters (char &lt;= 32) from both
+ * ends of this String, handling {@code null} by returning
+ * {@code null}.
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #strip(String)}.</p>
+ *
+ * <p>To trim your choice of characters, use the
+ * {@link #strip(String, String)} methods.</p>
+ *
+ * <pre>
+ * StringUtils.trim(null) = null
+ * StringUtils.trim("") = ""
+ * StringUtils.trim(" ") = ""
+ * StringUtils.trim("abc") = "abc"
+ * StringUtils.trim(" abc ") = "abc"
+ * </pre>
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed string, {@code null} if null String input
+ */
+ public static String trim(final String str) {
+ return str == null ? null : str.trim();
+ }
+
+ /**
+ * Removes control characters (char &lt;= 32) from both
+ * ends of this String returning an empty String ("") if the String
+ * is empty ("") after the trim or if it is {@code null}.
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #stripToEmpty(String)}.
+ *
+ * <pre>
+ * StringUtils.trimToEmpty(null) = ""
+ * StringUtils.trimToEmpty("") = ""
+ * StringUtils.trimToEmpty(" ") = ""
+ * StringUtils.trimToEmpty("abc") = "abc"
+ * StringUtils.trimToEmpty(" abc ") = "abc"
+ * </pre>
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String, or an empty String if {@code null} input
+ * @since 2.0
+ */
+ public static String trimToEmpty(final String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+
+ /**
+ * Removes control characters (char &lt;= 32) from both
+ * ends of this String returning {@code null} if the String is
+ * empty ("") after the trim or if it is {@code null}.
+ *
+ * <p>The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters &lt;= 32.
+ * To strip whitespace use {@link #stripToNull(String)}.
+ *
+ * <pre>
+ * StringUtils.trimToNull(null) = null
+ * StringUtils.trimToNull("") = null
+ * StringUtils.trimToNull(" ") = null
+ * StringUtils.trimToNull("abc") = "abc"
+ * StringUtils.trimToNull(" abc ") = "abc"
+ * </pre>
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String,
+ * {@code null} if only chars &lt;= 32, empty or null String input
+ * @since 2.0
+ */
+ public static String trimToNull(final String str) {
+ final String ts = trim(str);
+ return isEmpty(ts) ? null : ts;
+ }
+
+ /**
+ * Truncates a String. This will turn
+ * "Now is the time for all good men" into "Now is the time for".
+ *
+ * <p>Specifically:</p>
+ * <ul>
+ * <li>If {@code str} is less than {@code maxWidth} characters
+ * long, return it.</li>
+ * <li>Else truncate it to {@code substring(str, 0, maxWidth)}.</li>
+ * <li>If {@code maxWidth} is less than {@code 0}, throw an
+ * {@link IllegalArgumentException}.</li>
+ * <li>In no case will it return a String of length greater than
+ * {@code maxWidth}.</li>
+ * </ul>
+ *
+ * <pre>
+ * StringUtils.truncate(null, 0) = null
+ * StringUtils.truncate(null, 2) = null
+ * StringUtils.truncate("", 4) = ""
+ * StringUtils.truncate("abcdefg", 4) = "abcd"
+ * StringUtils.truncate("abcdefg", 6) = "abcdef"
+ * StringUtils.truncate("abcdefg", 7) = "abcdefg"
+ * StringUtils.truncate("abcdefg", 8) = "abcdefg"
+ * StringUtils.truncate("abcdefg", -1) = throws an IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to truncate, may be null
+ * @param maxWidth maximum length of result String, must be positive
+ * @return truncated String, {@code null} if null String input
+ * @throws IllegalArgumentException If {@code maxWidth} is less than {@code 0}
+ * @since 3.5
+ */
+ public static String truncate(final String str, final int maxWidth) {
+ return truncate(str, 0, maxWidth);
+ }
+
+ /**
+ * Truncates a String. This will turn
+ * "Now is the time for all good men" into "is the time for all".
+ *
+ * <p>Works like {@code truncate(String, int)}, but allows you to specify
+ * a "left edge" offset.
+ *
+ * <p>Specifically:</p>
+ * <ul>
+ * <li>If {@code str} is less than {@code maxWidth} characters
+ * long, return it.</li>
+ * <li>Else truncate it to {@code substring(str, offset, maxWidth)}.</li>
+ * <li>If {@code maxWidth} is less than {@code 0}, throw an
+ * {@link IllegalArgumentException}.</li>
+ * <li>If {@code offset} is less than {@code 0}, throw an
+ * {@link IllegalArgumentException}.</li>
+ * <li>In no case will it return a String of length greater than
+ * {@code maxWidth}.</li>
+ * </ul>
+ *
+ * <pre>
+ * StringUtils.truncate(null, 0, 0) = null
+ * StringUtils.truncate(null, 2, 4) = null
+ * StringUtils.truncate("", 0, 10) = ""
+ * StringUtils.truncate("", 2, 10) = ""
+ * StringUtils.truncate("abcdefghij", 0, 3) = "abc"
+ * StringUtils.truncate("abcdefghij", 5, 6) = "fghij"
+ * StringUtils.truncate("raspberry peach", 10, 15) = "peach"
+ * StringUtils.truncate("abcdefghijklmno", 0, 10) = "abcdefghij"
+ * StringUtils.truncate("abcdefghijklmno", -1, 10) = throws an IllegalArgumentException
+ * StringUtils.truncate("abcdefghijklmno", Integer.MIN_VALUE, 10) = throws an IllegalArgumentException
+ * StringUtils.truncate("abcdefghijklmno", Integer.MIN_VALUE, Integer.MAX_VALUE) = throws an IllegalArgumentException
+ * StringUtils.truncate("abcdefghijklmno", 0, Integer.MAX_VALUE) = "abcdefghijklmno"
+ * StringUtils.truncate("abcdefghijklmno", 1, 10) = "bcdefghijk"
+ * StringUtils.truncate("abcdefghijklmno", 2, 10) = "cdefghijkl"
+ * StringUtils.truncate("abcdefghijklmno", 3, 10) = "defghijklm"
+ * StringUtils.truncate("abcdefghijklmno", 4, 10) = "efghijklmn"
+ * StringUtils.truncate("abcdefghijklmno", 5, 10) = "fghijklmno"
+ * StringUtils.truncate("abcdefghijklmno", 5, 5) = "fghij"
+ * StringUtils.truncate("abcdefghijklmno", 5, 3) = "fgh"
+ * StringUtils.truncate("abcdefghijklmno", 10, 3) = "klm"
+ * StringUtils.truncate("abcdefghijklmno", 10, Integer.MAX_VALUE) = "klmno"
+ * StringUtils.truncate("abcdefghijklmno", 13, 1) = "n"
+ * StringUtils.truncate("abcdefghijklmno", 13, Integer.MAX_VALUE) = "no"
+ * StringUtils.truncate("abcdefghijklmno", 14, 1) = "o"
+ * StringUtils.truncate("abcdefghijklmno", 14, Integer.MAX_VALUE) = "o"
+ * StringUtils.truncate("abcdefghijklmno", 15, 1) = ""
+ * StringUtils.truncate("abcdefghijklmno", 15, Integer.MAX_VALUE) = ""
+ * StringUtils.truncate("abcdefghijklmno", Integer.MAX_VALUE, Integer.MAX_VALUE) = ""
+ * StringUtils.truncate("abcdefghij", 3, -1) = throws an IllegalArgumentException
+ * StringUtils.truncate("abcdefghij", -2, 4) = throws an IllegalArgumentException
+ * </pre>
+ *
+ * @param str the String to truncate, may be null
+ * @param offset left edge of source String
+ * @param maxWidth maximum length of result String, must be positive
+ * @return truncated String, {@code null} if null String input
+ * @throws IllegalArgumentException If {@code offset} or {@code maxWidth} is less than {@code 0}
+ * @since 3.5
+ */
+ public static String truncate(final String str, final int offset, final int maxWidth) {
+ if (offset < 0) {
+ throw new IllegalArgumentException("offset cannot be negative");
+ }
+ if (maxWidth < 0) {
+ throw new IllegalArgumentException("maxWith cannot be negative");
+ }
+ if (str == null) {
+ return null;
+ }
+ if (offset > str.length()) {
+ return EMPTY;
+ }
+ if (str.length() > maxWidth) {
+ final int ix = Math.min(offset + maxWidth, str.length());
+ return str.substring(offset, ix);
+ }
+ return str.substring(offset);
+ }
+
+ /**
+ * Uncapitalizes a String, changing the first character to lower case as
+ * per {@link Character#toLowerCase(int)}. No other characters are changed.
+ *
+ * <p>For a word based algorithm, see {@link org.apache.commons.text.WordUtils#uncapitalize(String)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.uncapitalize(null) = null
+ * StringUtils.uncapitalize("") = ""
+ * StringUtils.uncapitalize("cat") = "cat"
+ * StringUtils.uncapitalize("Cat") = "cat"
+ * StringUtils.uncapitalize("CAT") = "cAT"
+ * </pre>
+ *
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, {@code null} if null String input
+ * @see org.apache.commons.text.WordUtils#uncapitalize(String)
+ * @see #capitalize(String)
+ * @since 2.0
+ */
+ public static String uncapitalize(final String str) {
+ final int strLen = length(str);
+ if (strLen == 0) {
+ return str;
+ }
+
+ final int firstCodePoint = str.codePointAt(0);
+ final int newCodePoint = Character.toLowerCase(firstCodePoint);
+ if (firstCodePoint == newCodePoint) {
+ // already capitalized
+ return str;
+ }
+
+ final int[] newCodePoints = new int[strLen]; // cannot be longer than the char array
+ int outOffset = 0;
+ newCodePoints[outOffset++] = newCodePoint; // copy the first code point
+ for (int inOffset = Character.charCount(firstCodePoint); inOffset < strLen; ) {
+ final int codePoint = str.codePointAt(inOffset);
+ newCodePoints[outOffset++] = codePoint; // copy the remaining ones
+ inOffset += Character.charCount(codePoint);
+ }
+ return new String(newCodePoints, 0, outOffset);
+ }
+
+ /**
+ * Unwraps a given string from a character.
+ *
+ * <pre>
+ * StringUtils.unwrap(null, null) = null
+ * StringUtils.unwrap(null, '\0') = null
+ * StringUtils.unwrap(null, '1') = null
+ * StringUtils.unwrap("a", 'a') = "a"
+ * StringUtils.unwrap("aa", 'a') = ""
+ * StringUtils.unwrap("\'abc\'", '\'') = "abc"
+ * StringUtils.unwrap("AABabcBAA", 'A') = "ABabcBA"
+ * StringUtils.unwrap("A", '#') = "A"
+ * StringUtils.unwrap("#A", '#') = "#A"
+ * StringUtils.unwrap("A#", '#') = "A#"
+ * </pre>
+ *
+ * @param str
+ * the String to be unwrapped, can be null
+ * @param wrapChar
+ * the character used to unwrap
+ * @return unwrapped String or the original string
+ * if it is not quoted properly with the wrapChar
+ * @since 3.6
+ */
+ public static String unwrap(final String str, final char wrapChar) {
+ if (isEmpty(str) || wrapChar == CharUtils.NUL || str.length() == 1) {
+ return str;
+ }
+
+ if (str.charAt(0) == wrapChar && str.charAt(str.length() - 1) == wrapChar) {
+ final int startIndex = 0;
+ final int endIndex = str.length() - 1;
+
+ return str.substring(startIndex + 1, endIndex);
+ }
+
+ return str;
+ }
+
+ /**
+ * Unwraps a given string from another string.
+ *
+ * <pre>
+ * StringUtils.unwrap(null, null) = null
+ * StringUtils.unwrap(null, "") = null
+ * StringUtils.unwrap(null, "1") = null
+ * StringUtils.unwrap("a", "a") = "a"
+ * StringUtils.unwrap("aa", "a") = ""
+ * StringUtils.unwrap("\'abc\'", "\'") = "abc"
+ * StringUtils.unwrap("\"abc\"", "\"") = "abc"
+ * StringUtils.unwrap("AABabcBAA", "AA") = "BabcB"
+ * StringUtils.unwrap("A", "#") = "A"
+ * StringUtils.unwrap("#A", "#") = "#A"
+ * StringUtils.unwrap("A#", "#") = "A#"
+ * </pre>
+ *
+ * @param str
+ * the String to be unwrapped, can be null
+ * @param wrapToken
+ * the String used to unwrap
+ * @return unwrapped String or the original string
+ * if it is not quoted properly with the wrapToken
+ * @since 3.6
+ */
+ public static String unwrap(final String str, final String wrapToken) {
+ if (isEmpty(str) || isEmpty(wrapToken) || str.length() < 2 * wrapToken.length()) {
+ return str;
+ }
+
+ if (startsWith(str, wrapToken) && endsWith(str, wrapToken)) {
+ return str.substring(wrapToken.length(), str.lastIndexOf(wrapToken));
+ }
+
+ return str;
+ }
+
+ /**
+ * Converts a String to upper case as per {@link String#toUpperCase()}.
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.upperCase(null) = null
+ * StringUtils.upperCase("") = ""
+ * StringUtils.upperCase("aBc") = "ABC"
+ * </pre>
+ *
+ * <p><strong>Note:</strong> As described in the documentation for {@link String#toUpperCase()},
+ * the result of this method is affected by the current locale.
+ * For platform-independent case transformations, the method {@link #upperCase(String, Locale)}
+ * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).</p>
+ *
+ * @param str the String to upper case, may be null
+ * @return the upper-cased String, {@code null} if null String input
+ */
+ public static String upperCase(final String str) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase();
+ }
+
+ /**
+ * Converts a String to upper case as per {@link String#toUpperCase(Locale)}.
+ *
+ * <p>A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.upperCase(null, Locale.ENGLISH) = null
+ * StringUtils.upperCase("", Locale.ENGLISH) = ""
+ * StringUtils.upperCase("aBc", Locale.ENGLISH) = "ABC"
+ * </pre>
+ *
+ * @param str the String to upper case, may be null
+ * @param locale the locale that defines the case transformation rules, must not be null
+ * @return the upper-cased String, {@code null} if null String input
+ * @since 2.5
+ */
+ public static String upperCase(final String str, final Locale locale) {
+ if (str == null) {
+ return null;
+ }
+ return str.toUpperCase(LocaleUtils.toLocale(locale));
+ }
+
+ /**
+ * Returns the string representation of the {@code char} array or null.
+ *
+ * @param value the character array.
+ * @return a String or null
+ * @see String#valueOf(char[])
+ * @since 3.9
+ */
+ public static String valueOf(final char[] value) {
+ return value == null ? null : String.valueOf(value);
+ }
+
+ /**
+ * Wraps a string with a char.
+ *
+ * <pre>
+ * StringUtils.wrap(null, *) = null
+ * StringUtils.wrap("", *) = ""
+ * StringUtils.wrap("ab", '\0') = "ab"
+ * StringUtils.wrap("ab", 'x') = "xabx"
+ * StringUtils.wrap("ab", '\'') = "'ab'"
+ * StringUtils.wrap("\"ab\"", '\"') = "\"\"ab\"\""
+ * </pre>
+ *
+ * @param str
+ * the string to be wrapped, may be {@code null}
+ * @param wrapWith
+ * the char that will wrap {@code str}
+ * @return the wrapped string, or {@code null} if {@code str==null}
+ * @since 3.4
+ */
+ public static String wrap(final String str, final char wrapWith) {
+
+ if (isEmpty(str) || wrapWith == CharUtils.NUL) {
+ return str;
+ }
+
+ return wrapWith + str + wrapWith;
+ }
+
+ /**
+ * Wraps a String with another String.
+ *
+ * <p>
+ * A {@code null} input String returns {@code null}.
+ * </p>
+ *
+ * <pre>
+ * StringUtils.wrap(null, *) = null
+ * StringUtils.wrap("", *) = ""
+ * StringUtils.wrap("ab", null) = "ab"
+ * StringUtils.wrap("ab", "x") = "xabx"
+ * StringUtils.wrap("ab", "\"") = "\"ab\""
+ * StringUtils.wrap("\"ab\"", "\"") = "\"\"ab\"\""
+ * StringUtils.wrap("ab", "'") = "'ab'"
+ * StringUtils.wrap("'abcd'", "'") = "''abcd''"
+ * StringUtils.wrap("\"abcd\"", "'") = "'\"abcd\"'"
+ * StringUtils.wrap("'abcd'", "\"") = "\"'abcd'\""
+ * </pre>
+ *
+ * @param str
+ * the String to be wrapper, may be null
+ * @param wrapWith
+ * the String that will wrap str
+ * @return wrapped String, {@code null} if null String input
+ * @since 3.4
+ */
+ public static String wrap(final String str, final String wrapWith) {
+
+ if (isEmpty(str) || isEmpty(wrapWith)) {
+ return str;
+ }
+
+ return wrapWith.concat(str).concat(wrapWith);
+ }
+
+ /**
+ * Wraps a string with a char if that char is missing from the start or end of the given string.
+ *
+ * <p>A new {@link String} will not be created if {@code str} is already wrapped.</p>
+ *
+ * <pre>
+ * StringUtils.wrapIfMissing(null, *) = null
+ * StringUtils.wrapIfMissing("", *) = ""
+ * StringUtils.wrapIfMissing("ab", '\0') = "ab"
+ * StringUtils.wrapIfMissing("ab", 'x') = "xabx"
+ * StringUtils.wrapIfMissing("ab", '\'') = "'ab'"
+ * StringUtils.wrapIfMissing("\"ab\"", '\"') = "\"ab\""
+ * StringUtils.wrapIfMissing("/", '/') = "/"
+ * StringUtils.wrapIfMissing("a/b/c", '/') = "/a/b/c/"
+ * StringUtils.wrapIfMissing("/a/b/c", '/') = "/a/b/c/"
+ * StringUtils.wrapIfMissing("a/b/c/", '/') = "/a/b/c/"
+ * </pre>
+ *
+ * @param str
+ * the string to be wrapped, may be {@code null}
+ * @param wrapWith
+ * the char that will wrap {@code str}
+ * @return the wrapped string, or {@code null} if {@code str==null}
+ * @since 3.5
+ */
+ public static String wrapIfMissing(final String str, final char wrapWith) {
+ if (isEmpty(str) || wrapWith == CharUtils.NUL) {
+ return str;
+ }
+ final boolean wrapStart = str.charAt(0) != wrapWith;
+ final boolean wrapEnd = str.charAt(str.length() - 1) != wrapWith;
+ if (!wrapStart && !wrapEnd) {
+ return str;
+ }
+
+ final StringBuilder builder = new StringBuilder(str.length() + 2);
+ if (wrapStart) {
+ builder.append(wrapWith);
+ }
+ builder.append(str);
+ if (wrapEnd) {
+ builder.append(wrapWith);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Wraps a string with a string if that string is missing from the start or end of the given string.
+ *
+ * <p>A new {@link String} will not be created if {@code str} is already wrapped.</p>
+ *
+ * <pre>
+ * StringUtils.wrapIfMissing(null, *) = null
+ * StringUtils.wrapIfMissing("", *) = ""
+ * StringUtils.wrapIfMissing("ab", null) = "ab"
+ * StringUtils.wrapIfMissing("ab", "x") = "xabx"
+ * StringUtils.wrapIfMissing("ab", "\"") = "\"ab\""
+ * StringUtils.wrapIfMissing("\"ab\"", "\"") = "\"ab\""
+ * StringUtils.wrapIfMissing("ab", "'") = "'ab'"
+ * StringUtils.wrapIfMissing("'abcd'", "'") = "'abcd'"
+ * StringUtils.wrapIfMissing("\"abcd\"", "'") = "'\"abcd\"'"
+ * StringUtils.wrapIfMissing("'abcd'", "\"") = "\"'abcd'\""
+ * StringUtils.wrapIfMissing("/", "/") = "/"
+ * StringUtils.wrapIfMissing("a/b/c", "/") = "/a/b/c/"
+ * StringUtils.wrapIfMissing("/a/b/c", "/") = "/a/b/c/"
+ * StringUtils.wrapIfMissing("a/b/c/", "/") = "/a/b/c/"
+ * </pre>
+ *
+ * @param str
+ * the string to be wrapped, may be {@code null}
+ * @param wrapWith
+ * the string that will wrap {@code str}
+ * @return the wrapped string, or {@code null} if {@code str==null}
+ * @since 3.5
+ */
+ public static String wrapIfMissing(final String str, final String wrapWith) {
+ if (isEmpty(str) || isEmpty(wrapWith)) {
+ return str;
+ }
+
+ final boolean wrapStart = !str.startsWith(wrapWith);
+ final boolean wrapEnd = !str.endsWith(wrapWith);
+ if (!wrapStart && !wrapEnd) {
+ return str;
+ }
+
+ final StringBuilder builder = new StringBuilder(str.length() + wrapWith.length() + wrapWith.length());
+ if (wrapStart) {
+ builder.append(wrapWith);
+ }
+ builder.append(str);
+ if (wrapEnd) {
+ builder.append(wrapWith);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * {@link StringUtils} instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * {@code StringUtils.trim(" foo ");}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public StringUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/SystemProperties.java b/src/main/java/org/apache/commons/lang3/SystemProperties.java
new file mode 100644
index 000000000..221c7c973
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/SystemProperties.java
@@ -0,0 +1,791 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import java.util.function.Supplier;
+
+/**
+ * Accesses current system property names and values.
+ *
+ * @since 3.13.0
+ */
+public final class SystemProperties {
+
+ private static final Supplier<String> NULL_SUPPLIER = () -> null;
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String AWT_TOOLKIT = "awt.toolkit";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String FILE_ENCODING = "file.encoding";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String FILE_SEPARATOR = "file.separator";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_AWT_FONTS = "java.awt.fonts";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_AWT_GRAPHICSENV = "java.awt.graphicsenv";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_AWT_HEADLESS = "java.awt.headless";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_AWT_PRINTERJOB = "java.awt.printerjob";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_CLASS_PATH = "java.class.path";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_CLASS_VERSION = "java.class.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_COMPILER = "java.compiler";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_ENDORSED_DIRS = "java.endorsed.dirs";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_EXT_DIRS = "java.ext.dirs";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_HOME = "java.home";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_LIBRARY_PATH = "java.library.path";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_RUNTIME_NAME = "java.runtime.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_RUNTIME_VERSION = "java.runtime.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_SPECIFICATION_NAME = "java.specification.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_SPECIFICATION_VENDOR = "java.specification.vendor";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_SPECIFICATION_VERSION = "java.specification.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY = "java.util.prefs.PreferencesFactory";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VENDOR = "java.vendor";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VENDOR_URL = "java.vendor.url";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VERSION = "java.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_INFO = "java.vm.info";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_NAME = "java.vm.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_SPECIFICATION_NAME = "java.vm.specification.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_SPECIFICATION_VENDOR = "java.vm.specification.vendor";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_SPECIFICATION_VERSION = "java.vm.specification.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_VENDOR = "java.vm.vendor";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String JAVA_VM_VERSION = "java.vm.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String LINE_SEPARATOR = "line.separator";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String OS_ARCH = "os.arch";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String OS_NAME = "os.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String OS_VERSION = "os.version";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String PATH_SEPARATOR = "path.separator";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_COUNTRY = "user.country";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_DIR = "user.dir";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_HOME = "user.home";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_LANGUAGE = "user.language";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_NAME = "user.name";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_REGION = "user.region";
+
+ /**
+ * The System property name {@value}.
+ */
+ public static final String USER_TIMEZONE = "user.timezone";
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getAwtToolkit() {
+ return getProperty(AWT_TOOLKIT);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getFileEncoding() {
+ return getProperty(FILE_ENCODING);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getFileSeparator() {
+ return getProperty(FILE_SEPARATOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaAwtFonts() {
+ return getProperty(JAVA_AWT_FONTS);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaAwtGraphicsenv() {
+ return getProperty(JAVA_AWT_GRAPHICSENV);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaAwtHeadless() {
+ return getProperty(JAVA_AWT_HEADLESS);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaAwtPrinterjob() {
+ return getProperty(JAVA_AWT_PRINTERJOB);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaClassPath() {
+ return getProperty(JAVA_CLASS_PATH);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaClassVersion() {
+ return getProperty(JAVA_CLASS_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaCompiler() {
+ return getProperty(JAVA_COMPILER);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaEndorsedDirs() {
+ return getProperty(JAVA_ENDORSED_DIRS);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaExtDirs() {
+ return getProperty(JAVA_EXT_DIRS);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaHome() {
+ return getProperty(JAVA_HOME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaIoTmpdir() {
+ return getProperty(JAVA_IO_TMPDIR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaLibraryPath() {
+ return getProperty(JAVA_LIBRARY_PATH);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaRuntimeName() {
+ return getProperty(JAVA_RUNTIME_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaRuntimeVersion() {
+ return getProperty(JAVA_RUNTIME_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaSpecificationName() {
+ return getProperty(JAVA_SPECIFICATION_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaSpecificationVendor() {
+ return getProperty(JAVA_SPECIFICATION_VENDOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaSpecificationVersion() {
+ return getProperty(JAVA_SPECIFICATION_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaUtilPrefsPreferencesFactory() {
+ return getProperty(JAVA_UTIL_PREFS_PREFERENCES_FACTORY);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVendor() {
+ return getProperty(JAVA_VENDOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVendorUrl() {
+ return getProperty(JAVA_VENDOR_URL);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVersion() {
+ return getProperty(JAVA_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmInfo() {
+ return getProperty(JAVA_VM_INFO);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmName() {
+ return getProperty(JAVA_VM_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmSpecificationName() {
+ return getProperty(JAVA_VM_SPECIFICATION_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmSpecificationVendor() {
+ return getProperty(JAVA_VM_SPECIFICATION_VENDOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmSpecificationVersion() {
+ return getProperty(JAVA_VM_SPECIFICATION_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmVendor() {
+ return getProperty(JAVA_VM_VENDOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getJavaVmVersion() {
+ return getProperty(JAVA_VM_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getLineSeparator() {
+ return getProperty(LINE_SEPARATOR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getOsArch() {
+ return getProperty(OS_ARCH);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getOsName() {
+ return getProperty(OS_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getOsVersion() {
+ return getProperty(OS_VERSION);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getPathSeparator() {
+ return getProperty(PATH_SEPARATOR);
+ }
+
+ /**
+ * Gets a System property, defaulting to {@code null} if the property cannot be read.
+ * <p>
+ * If a {@link SecurityException} is caught, the return value is {@code null}.
+ * </p>
+ *
+ * @param property the system property name
+ * @return the system property value or {@code null} if a security problem occurs
+ */
+ public static String getProperty(final String property) {
+ return getProperty(property, NULL_SUPPLIER);
+ }
+
+ /**
+ * Gets a System property, defaulting to {@code null} if the property cannot be read.
+ * <p>
+ * If a {@link SecurityException} is caught, the return value is {@code null}.
+ * </p>
+ *
+ * @param property the system property name.
+ * @param defaultValue get this Supplier when the property is empty or throws SecurityException.
+ * @return the system property value or {@code null} if a security problem occurs
+ */
+ static String getProperty(final String property, final Supplier<String> defaultValue) {
+ try {
+ if (StringUtils.isEmpty(property)) {
+ return defaultValue.get();
+ }
+ final String value = System.getProperty(property);
+ return StringUtils.getIfEmpty(value, defaultValue);
+ } catch (final SecurityException ignore) {
+ // We are not allowed to look at this property.
+ //
+ // System.err.println("Caught a SecurityException reading the system property '" + property
+ // + "'; the SystemUtils property value will default to null.");
+ return defaultValue.get();
+ }
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserCountry() {
+ return getProperty(USER_COUNTRY);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserDir() {
+ return getProperty(USER_DIR);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserHome() {
+ return getProperty(USER_HOME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserLanguage() {
+ return getProperty(USER_LANGUAGE);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserName() {
+ return getProperty(USER_NAME);
+ }
+
+ /**
+ * Gets the current value from the system properties map.
+ * <p>
+ * Returns {@code null} if the property cannot be read due to a {@link SecurityException}.
+ * </p>
+ *
+ * @return the current value from the system properties map.
+ */
+ public static String getUserTimezone() {
+ return getProperty(USER_TIMEZONE);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/SystemUtils.java b/src/main/java/org/apache/commons/lang3/SystemUtils.java
new file mode 100644
index 000000000..85a2bb443
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/SystemUtils.java
@@ -0,0 +1,2094 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.io.File;
+
+/**
+ * Helpers for {@code java.lang.System}.
+ *
+ * <p>
+ * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set
+ * to {@code null} and a message will be written to {@code System.err}.
+ * </p>
+ * <p>
+ * #ThreadSafe#
+ * </p>
+ *
+ * @since 1.0
+ * @see SystemProperties
+ */
+public class SystemUtils {
+
+ /**
+ * The prefix String for all Windows OS.
+ */
+ private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
+
+ // System property constants
+ // -----------------------------------------------------------------------
+ // These MUST be declared first. Other constants depend on this.
+
+ /**
+ * The {@code file.encoding} System Property.
+ *
+ * <p>
+ * File encoding, such as {@code Cp1252}.
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getFileEncoding()
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String FILE_ENCODING = SystemProperties.getFileEncoding();
+
+ /**
+ * The {@code file.separator} System Property.
+ * The file separator is:
+ *
+ * <ul>
+ * <li>{@code "/"} on UNIX</li>
+ * <li>{@code "\"} on Windows.</li>
+ * </ul>
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getFileSeparator()
+ * @deprecated Use {@link File#separator}, since it is guaranteed to be a
+ * string containing a single character and it does not require a privilege check.
+ * @since Java 1.1
+ */
+ @Deprecated
+ public static final String FILE_SEPARATOR = SystemProperties.getFileSeparator();
+
+ /**
+ * The {@code java.awt.fonts} System Property.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaAwtFonts()
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_FONTS = SystemProperties.getJavaAwtFonts();
+
+ /**
+ * The {@code java.awt.graphicsenv} System Property.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaAwtGraphicsenv()
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_GRAPHICSENV = SystemProperties.getJavaAwtGraphicsenv();
+
+ /**
+ * The {@code java.awt.headless} System Property. The value of this property is the String {@code "true"} or
+ * {@code "false"}.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see #isJavaAwtHeadless()
+ * @see SystemProperties#getJavaAwtHeadless()
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_AWT_HEADLESS = SystemProperties.getJavaAwtHeadless();
+
+ /**
+ * The {@code java.awt.printerjob} System Property.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaAwtPrinterjob()
+ * @since 2.1
+ */
+ public static final String JAVA_AWT_PRINTERJOB = SystemProperties.getJavaAwtPrinterjob();
+
+ /**
+ * The {@code java.class.path} System Property. Java class path.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaClassPath()
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_PATH = SystemProperties.getJavaClassPath();
+
+ /**
+ * The {@code java.class.version} System Property. Java class format version number.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaClassVersion()
+ * @since Java 1.1
+ */
+ public static final String JAVA_CLASS_VERSION = SystemProperties.getJavaClassVersion();
+
+ /**
+ * The {@code java.compiler} System Property. Name of JIT compiler to use. First in JDK version 1.2. Not used in Sun
+ * JDKs after 1.2.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaCompiler()
+ * @since Java 1.2. Not used in Sun versions after 1.2.
+ */
+ public static final String JAVA_COMPILER = SystemProperties.getJavaCompiler();
+
+ /**
+ * The {@code java.endorsed.dirs} System Property. Path of endorsed directory or directories.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaEndorsedDirs()
+ * @since Java 1.4
+ */
+ public static final String JAVA_ENDORSED_DIRS = SystemProperties.getJavaEndorsedDirs();
+
+ /**
+ * The {@code java.ext.dirs} System Property. Path of extension directory or directories.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaExtDirs()
+ * @since Java 1.3
+ */
+ public static final String JAVA_EXT_DIRS = SystemProperties.getJavaExtDirs();
+
+ /**
+ * The {@code java.home} System Property. Java installation directory.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaHome()
+ * @since Java 1.1
+ */
+ public static final String JAVA_HOME = SystemProperties.getJavaHome();
+
+ /**
+ * The {@code java.io.tmpdir} System Property. Default temp file path.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaIoTmpdir()
+ * @since Java 1.2
+ */
+ public static final String JAVA_IO_TMPDIR = SystemProperties.getJavaIoTmpdir();
+
+ /**
+ * The {@code java.library.path} System Property. List of paths to search when loading libraries.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaLibraryPath()
+ * @since Java 1.2
+ */
+ public static final String JAVA_LIBRARY_PATH = SystemProperties.getJavaLibraryPath();
+
+ /**
+ * The {@code java.runtime.name} System Property. Java Runtime Environment name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaRuntimeName()
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_NAME = SystemProperties.getJavaRuntimeName();
+
+ /**
+ * The {@code java.runtime.version} System Property. Java Runtime Environment version.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaRuntimeVersion()
+ * @since 2.0
+ * @since Java 1.3
+ */
+ public static final String JAVA_RUNTIME_VERSION = SystemProperties.getJavaRuntimeVersion();
+
+ /**
+ * The {@code java.specification.name} System Property. Java Runtime Environment specification name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaSpecificationName()
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_NAME = SystemProperties.getJavaSpecificationName();
+
+ /**
+ * The {@code java.specification.vendor} System Property. Java Runtime Environment specification vendor.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaSpecificationVendor()
+ * @since Java 1.2
+ */
+ public static final String JAVA_SPECIFICATION_VENDOR = SystemProperties.getJavaSpecificationVendor();
+
+ /**
+ * The {@code java.specification.version} System Property. Java Runtime Environment specification version.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaSpecificationVersion()
+ * @since Java 1.3
+ */
+ public static final String JAVA_SPECIFICATION_VERSION = SystemProperties.getJavaSpecificationVersion();
+
+ private static final JavaVersion JAVA_SPECIFICATION_VERSION_AS_ENUM = JavaVersion.get(JAVA_SPECIFICATION_VERSION);
+
+ /**
+ * The {@code java.util.prefs.PreferencesFactory} System Property. A class name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaUtilPrefsPreferencesFactory()
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static final String JAVA_UTIL_PREFS_PREFERENCES_FACTORY = SystemProperties.getJavaUtilPrefsPreferencesFactory();
+
+ /**
+ * The {@code java.vendor} System Property. Java vendor-specific string.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVersion()
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR = SystemProperties.getJavaVersion();
+
+ /**
+ * The {@code java.vendor.url} System Property. Java vendor URL.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVendorUrl()
+ * @since Java 1.1
+ */
+ public static final String JAVA_VENDOR_URL = SystemProperties.getJavaVendorUrl();
+
+ /**
+ * The {@code java.version} System Property. Java version number.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVersion()
+ * @since Java 1.1
+ */
+ public static final String JAVA_VERSION = SystemProperties.getJavaVersion();
+
+ /**
+ * The {@code java.vm.info} System Property. Java Virtual Machine implementation info.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmInfo()
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_INFO = SystemProperties.getJavaVmInfo();
+
+ /**
+ * The {@code java.vm.name} System Property. Java Virtual Machine implementation name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmName()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_NAME = SystemProperties.getJavaVmName();
+
+ /**
+ * The {@code java.vm.specification.name} System Property. Java Virtual Machine specification name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmSpecificationName()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_NAME = SystemProperties.getJavaVmSpecificationName();
+
+ /**
+ * The {@code java.vm.specification.vendor} System Property. Java Virtual Machine specification vendor.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmSpecificationVendor()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VENDOR = SystemProperties.getJavaVmSpecificationVendor();
+
+ /**
+ * The {@code java.vm.specification.version} System Property. Java Virtual Machine specification version.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmSpecificationVersion()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_SPECIFICATION_VERSION = SystemProperties.getJavaVmSpecificationVersion();
+
+ /**
+ * The {@code java.vm.vendor} System Property. Java Virtual Machine implementation vendor.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmVendor()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VENDOR = SystemProperties.getJavaVmVendor();
+
+ /**
+ * The {@code java.vm.version} System Property. Java Virtual Machine implementation version.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getJavaVmVersion()
+ * @since Java 1.2
+ */
+ public static final String JAVA_VM_VERSION = SystemProperties.getJavaVmVersion();
+
+ /**
+ * The {@code line.separator} System Property. Line separator ({@code &quot;\n&quot;} on UNIX).
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getLineSeparator()
+ * @deprecated Use {@link System#lineSeparator()} instead, since it does not require a privilege check.
+ * @since Java 1.1
+ */
+ @Deprecated
+ public static final String LINE_SEPARATOR = SystemProperties.getLineSeparator();
+
+ /**
+ * The {@code os.arch} System Property. Operating system architecture.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getOsArch()
+ * @since Java 1.1
+ */
+ public static final String OS_ARCH = SystemProperties.getOsArch();
+
+ /**
+ * The {@code os.name} System Property. Operating system name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getOsName()
+ * @since Java 1.1
+ */
+ public static final String OS_NAME = SystemProperties.getOsName();
+
+ /**
+ * The {@code os.version} System Property. Operating system version.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getOsVersion()
+ * @since Java 1.1
+ */
+ public static final String OS_VERSION = SystemProperties.getOsVersion();
+
+ /**
+ * The {@code path.separator} System Property. Path separator ({@code &quot;:&quot;} on UNIX).
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getPathSeparator()
+ * @deprecated Use {@link File#pathSeparator}, since it is guaranteed to be a
+ * string containing a single character and it does not require a privilege check.
+ * @since Java 1.1
+ */
+ @Deprecated
+ public static final String PATH_SEPARATOR = SystemProperties.getPathSeparator();
+
+ /**
+ * The {@code user.country} or {@code user.region} System Property. User's country code, such as {@code "GB"}. First
+ * in Java version 1.2 as {@code user.region}. Renamed to {@code user.country} in 1.4
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_COUNTRY = SystemProperties.getProperty(SystemProperties.USER_COUNTRY,
+ () -> SystemProperties.getProperty(SystemProperties.USER_REGION));
+
+ /**
+ * The {@code user.dir} System Property. User's current working directory.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getUserDir()
+ * @since Java 1.1
+ */
+ public static final String USER_DIR = SystemProperties.getUserDir();
+
+ /**
+ * The {@code user.home} System Property. User's home directory.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getUserHome()
+ * @since Java 1.1
+ */
+ public static final String USER_HOME = SystemProperties.getUserHome();
+
+ /**
+ * The {@code user.language} System Property. User's language code, such as {@code "en"}.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getUserLanguage()
+ * @since 2.0
+ * @since Java 1.2
+ */
+ public static final String USER_LANGUAGE = SystemProperties.getUserLanguage();
+
+ /**
+ * The {@code user.name} System Property. User's account name.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getUserName()
+ * @since Java 1.1
+ */
+ public static final String USER_NAME = SystemProperties.getUserName();
+
+ /**
+ * The {@code user.timezone} System Property. For example: {@code "America/Los_Angeles"}.
+ *
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @see SystemProperties#getUserTimezone()
+ * @since 2.1
+ */
+ public static final String USER_TIMEZONE = SystemProperties.getUserTimezone();
+
+ // Java version checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+
+ /**
+ * Is {@code true} if this is Java version 1.1 (also 1.1.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_1 = getJavaVersionMatches("1.1");
+
+ /**
+ * Is {@code true} if this is Java version 1.2 (also 1.2.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_2 = getJavaVersionMatches("1.2");
+
+ /**
+ * Is {@code true} if this is Java version 1.3 (also 1.3.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_3 = getJavaVersionMatches("1.3");
+
+ /**
+ * Is {@code true} if this is Java version 1.4 (also 1.4.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_4 = getJavaVersionMatches("1.4");
+
+ /**
+ * Is {@code true} if this is Java version 1.5 (also 1.5.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_5 = getJavaVersionMatches("1.5");
+
+ /**
+ * Is {@code true} if this is Java version 1.6 (also 1.6.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ */
+ public static final boolean IS_JAVA_1_6 = getJavaVersionMatches("1.6");
+
+ /**
+ * Is {@code true} if this is Java version 1.7 (also 1.7.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_JAVA_1_7 = getJavaVersionMatches("1.7");
+
+ /**
+ * Is {@code true} if this is Java version 1.8 (also 1.8.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.3.2
+ */
+ public static final boolean IS_JAVA_1_8 = getJavaVersionMatches("1.8");
+
+ /**
+ * Is {@code true} if this is Java version 1.9 (also 1.9.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ *
+ * @deprecated As of release 3.5, replaced by {@link #IS_JAVA_9}
+ */
+ @Deprecated
+ public static final boolean IS_JAVA_1_9 = getJavaVersionMatches("9");
+
+ /**
+ * Is {@code true} if this is Java version 9 (also 9.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final boolean IS_JAVA_9 = getJavaVersionMatches("9");
+
+ /**
+ * Is {@code true} if this is Java version 10 (also 10.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.7
+ */
+ public static final boolean IS_JAVA_10 = getJavaVersionMatches("10");
+
+ /**
+ * Is {@code true} if this is Java version 11 (also 11.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.8
+ */
+ public static final boolean IS_JAVA_11 = getJavaVersionMatches("11");
+
+ /**
+ * Is {@code true} if this is Java version 12 (also 12.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.9
+ */
+ public static final boolean IS_JAVA_12 = getJavaVersionMatches("12");
+
+ /**
+ * Is {@code true} if this is Java version 13 (also 13.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.9
+ */
+ public static final boolean IS_JAVA_13 = getJavaVersionMatches("13");
+
+ /**
+ * Is {@code true} if this is Java version 14 (also 14.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.10
+ */
+ public static final boolean IS_JAVA_14 = getJavaVersionMatches("14");
+
+ /**
+ * Is {@code true} if this is Java version 15 (also 15.x versions).
+ *
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.10
+ */
+ public static final boolean IS_JAVA_15 = getJavaVersionMatches("15");
+
+ /**
+ * Is {@code true} if this is Java version 16 (also 16.x versions).
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+ public static final boolean IS_JAVA_16 = getJavaVersionMatches("16");
+
+ /**
+ * Is {@code true} if this is Java version 17 (also 17.x versions).
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+ public static final boolean IS_JAVA_17 = getJavaVersionMatches("17");
+
+ /**
+ * Is {@code true} if this is Java version 18 (also 18.x versions).
+ * <p>
+ * The field will return {@code false} if {@link #JAVA_VERSION} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+ public static final boolean IS_JAVA_18 = getJavaVersionMatches("18");
+
+ // Operating system checks
+ // -----------------------------------------------------------------------
+ // These MUST be declared after those above as they depend on the
+ // values being set up
+ // Please advise dev@commons.apache.org if you want another added
+ // or a mistake corrected
+
+ /**
+ * Is {@code true} if this is AIX.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_AIX = getOsMatchesName("AIX");
+
+ /**
+ * Is {@code true} if this is HP-UX.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_HP_UX = getOsMatchesName("HP-UX");
+
+ /**
+ * Is {@code true} if this is IBM OS/400.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.3
+ */
+ public static final boolean IS_OS_400 = getOsMatchesName("OS/400");
+
+ /**
+ * Is {@code true} if this is Irix.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_IRIX = getOsMatchesName("Irix");
+
+ /**
+ * Is {@code true} if this is Linux.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_LINUX = getOsMatchesName("Linux") || getOsMatchesName("LINUX");
+
+ /**
+ * Is {@code true} if this is Mac.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC = getOsMatchesName("Mac");
+
+ /**
+ * Is {@code true} if this is Mac.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_MAC_OSX = getOsMatchesName("Mac OS X");
+
+ /**
+ * Is {@code true} if this is Mac OS X Cheetah.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_CHEETAH = getOsMatches("Mac OS X", "10.0");
+
+ /**
+ * Is {@code true} if this is Mac OS X Puma.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_PUMA = getOsMatches("Mac OS X", "10.1");
+
+ /**
+ * Is {@code true} if this is Mac OS X Jaguar.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_JAGUAR = getOsMatches("Mac OS X", "10.2");
+
+ /**
+ * Is {@code true} if this is Mac OS X Panther.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_PANTHER = getOsMatches("Mac OS X", "10.3");
+
+ /**
+ * Is {@code true} if this is Mac OS X Tiger.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_TIGER = getOsMatches("Mac OS X", "10.4");
+
+ /**
+ * Is {@code true} if this is Mac OS X Leopard.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_LEOPARD = getOsMatches("Mac OS X", "10.5");
+
+ /**
+ * Is {@code true} if this is Mac OS X Snow Leopard.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_SNOW_LEOPARD = getOsMatches("Mac OS X", "10.6");
+
+ /**
+ * Is {@code true} if this is Mac OS X Lion.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_LION = getOsMatches("Mac OS X", "10.7");
+
+ /**
+ * Is {@code true} if this is Mac OS X Mountain Lion.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_MOUNTAIN_LION = getOsMatches("Mac OS X", "10.8");
+
+ /**
+ * Is {@code true} if this is Mac OS X Mavericks.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_MAVERICKS = getOsMatches("Mac OS X", "10.9");
+
+ /**
+ * Is {@code true} if this is Mac OS X Yosemite.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_MAC_OSX_YOSEMITE = getOsMatches("Mac OS X", "10.10");
+
+ /**
+ * Is {@code true} if this is Mac OS X El Capitan.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final boolean IS_OS_MAC_OSX_EL_CAPITAN = getOsMatches("Mac OS X", "10.11");
+
+ /**
+ * Is {@code true} if this is Mac OS X Sierra.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.12.0
+ */
+ public static final boolean IS_OS_MAC_OSX_SIERRA = getOsMatches("Mac OS X", "10.12");
+
+ /**
+ * Is {@code true} if this is Mac OS X High Sierra.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.12.0
+ */
+ public static final boolean IS_OS_MAC_OSX_HIGH_SIERRA = getOsMatches("Mac OS X", "10.13");
+
+ /**
+ * Is {@code true} if this is Mac OS X Mojave.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.12.0
+ */
+ public static final boolean IS_OS_MAC_OSX_MOJAVE = getOsMatches("Mac OS X", "10.14");
+
+ /**
+ * Is {@code true} if this is Mac OS X Catalina.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.12.0
+ */
+ public static final boolean IS_OS_MAC_OSX_CATALINA = getOsMatches("Mac OS X", "10.15");
+
+ /**
+ * Is {@code true} if this is Mac OS X Big Sur.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.12.0
+ */
+ public static final boolean IS_OS_MAC_OSX_BIG_SUR = getOsMatches("Mac OS X", "10.16");
+
+ /**
+ * Is {@code true} if this is FreeBSD.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_FREE_BSD = getOsMatchesName("FreeBSD");
+
+ /**
+ * Is {@code true} if this is OpenBSD.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_OPEN_BSD = getOsMatchesName("OpenBSD");
+
+ /**
+ * Is {@code true} if this is NetBSD.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_NET_BSD = getOsMatchesName("NetBSD");
+
+ /**
+ * Is {@code true} if this is OS/2.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_OS2 = getOsMatchesName("OS/2");
+
+ /**
+ * Is {@code true} if this is Solaris.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SOLARIS = getOsMatchesName("Solaris");
+
+ /**
+ * Is {@code true} if this is SunOS.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_SUN_OS = getOsMatchesName("SunOS");
+
+ /**
+ * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX, Irix, Linux, MacOSX, Solaris or SUN OS.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.1
+ */
+ public static final boolean IS_OS_UNIX = IS_OS_AIX || IS_OS_HP_UX || IS_OS_IRIX || IS_OS_LINUX || IS_OS_MAC_OSX
+ || IS_OS_SOLARIS || IS_OS_SUN_OS || IS_OS_FREE_BSD || IS_OS_OPEN_BSD || IS_OS_NET_BSD;
+
+ /**
+ * Is {@code true} if this is Windows.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS = getOsMatchesName(OS_NAME_WINDOWS_PREFIX);
+
+ /**
+ * Is {@code true} if this is Windows 2000.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_2000 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 2000");
+
+ /**
+ * Is {@code true} if this is Windows 2003.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_WINDOWS_2003 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 2003");
+
+ /**
+ * Is {@code true} if this is Windows Server 2008.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.1
+ */
+ public static final boolean IS_OS_WINDOWS_2008 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " Server 2008");
+
+ /**
+ * Is {@code true} if this is Windows Server 2012.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.4
+ */
+ public static final boolean IS_OS_WINDOWS_2012 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " Server 2012");
+
+ /**
+ * Is {@code true} if this is Windows 95.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_95 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 95");
+
+ /**
+ * Is {@code true} if this is Windows 98.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_98 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 98");
+
+ /**
+ * Is {@code true} if this is Windows ME.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_ME = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " Me");
+
+ /**
+ * Is {@code true} if this is Windows NT.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_NT = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " NT");
+
+ /**
+ * Is {@code true} if this is Windows XP.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_XP = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " XP");
+
+ /**
+ * Is {@code true} if this is Windows Vista.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 2.4
+ */
+ public static final boolean IS_OS_WINDOWS_VISTA = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " Vista");
+
+ /**
+ * Is {@code true} if this is Windows 7.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public static final boolean IS_OS_WINDOWS_7 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 7");
+
+ /**
+ * Is {@code true} if this is Windows 8.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.2
+ */
+ public static final boolean IS_OS_WINDOWS_8 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 8");
+
+ /**
+ * Is {@code true} if this is Windows 10.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final boolean IS_OS_WINDOWS_10 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 10");
+
+ /**
+ * Is {@code true} if this is Windows 11.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * OpenJDK fixed the return value for {@code os.name} on Windows 11 to versions 8, 11, and 17:
+ * </p>
+ * <ul>
+ * <li>Affects Java versions 7u321, 8u311, 11.0.13-oracle, 17.0.1: https://bugs.openjdk.org/browse/JDK-8274737</li>
+ * <li>Fixed in OpenJDK commit https://github.com/openjdk/jdk/commit/97ea9dd2f24f9f1fb9b9345a4202a825ee28e014</li>
+ * </ul>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+ public static final boolean IS_OS_WINDOWS_11 = getOsMatchesName(OS_NAME_WINDOWS_PREFIX + " 11");
+
+ /**
+ * Is {@code true} if this is z/OS.
+ *
+ * <p>
+ * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded.
+ * </p>
+ *
+ * @since 3.5
+ */
+ // Values on a z/OS system I tested (Gary Gregory - 2016-03-12)
+ // os.arch = s390x
+ // os.encoding = ISO8859_1
+ // os.name = z/OS
+ // os.version = 02.02.00
+ public static final boolean IS_OS_ZOS = getOsMatchesName("z/OS");
+
+ /**
+ * The System property key for the user home directory.
+ */
+ public static final String USER_HOME_KEY = "user.home";
+
+ /**
+ * The System property key for the user name.
+ *
+ * @deprecated Use {@link SystemProperties#USER_NAME}.
+ */
+ @Deprecated
+ public static final String USER_NAME_KEY = "user.name";
+
+ /**
+ * The System property key for the user directory.
+ *
+ * @deprecated Use {@link SystemProperties#USER_DIR}.
+ */
+ @Deprecated
+ public static final String USER_DIR_KEY = "user.dir";
+
+ /**
+ * The System property key for the Java IO temporary directory.
+ *
+ * @deprecated Use {@link SystemProperties#JAVA_IO_TMPDIR}.
+ */
+ @Deprecated
+ public static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
+
+ /**
+ * The System property key for the Java home directory.
+ *
+ * @deprecated Use {@link SystemProperties#JAVA_HOME}.
+ */
+ @Deprecated
+ public static final String JAVA_HOME_KEY = "java.home";
+
+ /**
+ * The {@code awt.toolkit} System Property.
+ *
+ * <p>
+ * Holds a class name, on Windows XP this is {@code sun.awt.windows.WToolkit}.
+ * </p>
+ * <p>
+ * <b>On platforms without a GUI, this value is {@code null}.</b>
+ * </p>
+ * <p>
+ * Defaults to {@code null} if the runtime does not have security access to read this property or the property does
+ * not exist.
+ * </p>
+ * <p>
+ * This value is initialized when the class is loaded. If {@link System#setProperty(String,String)} or
+ * {@link System#setProperties(java.util.Properties)} is called after this class is loaded, the value will be out of
+ * sync with that System property.
+ * </p>
+ *
+ * @since 2.1
+ * @see SystemProperties#getAwtToolkit()
+ */
+ public static final String AWT_TOOLKIT = SystemProperties.getAwtToolkit();
+
+ /**
+ * Gets an environment variable, defaulting to {@code defaultValue} if the variable cannot be read.
+ *
+ * <p>
+ * If a {@link SecurityException} is caught, the return value is {@code defaultValue} and a message is written to
+ * {@code System.err}.
+ * </p>
+ *
+ * @param name
+ * the environment variable name
+ * @param defaultValue
+ * the default value
+ * @return the environment variable value or {@code defaultValue} if a security problem occurs
+ * @since 3.8
+ */
+ public static String getEnvironmentVariable(final String name, final String defaultValue) {
+ try {
+ final String value = System.getenv(name);
+ return value == null ? defaultValue : value;
+ } catch (final SecurityException ex) {
+ // we are not allowed to look at this property
+ // System.err.println("Caught a SecurityException reading the environment variable '" + name + "'.");
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Gets the host name from an environment variable
+ * (COMPUTERNAME on Windows, HOSTNAME elsewhere).
+ *
+ * <p>
+ * If you want to know what the network stack says is the host name, you should use {@code InetAddress.getLocalHost().getHostName()}.
+ * </p>
+ *
+ * @return the host name. Will be {@code null} if the environment variable is not defined.
+ * @since 3.6
+ */
+ public static String getHostName() {
+ return IS_OS_WINDOWS ? System.getenv("COMPUTERNAME") : System.getenv("HOSTNAME");
+ }
+
+ /**
+ * Gets the current Java home directory as a {@link File}.
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getJavaHome()
+ * @since 2.1
+ */
+ public static File getJavaHome() {
+ return new File(SystemProperties.getJavaHome());
+ }
+
+ /**
+ * Gets the current Java IO temporary directory as a {@link File}.
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getJavaIoTmpdir()
+ * @since 2.1
+ */
+ public static File getJavaIoTmpDir() {
+ return new File(SystemProperties.getJavaIoTmpdir());
+ }
+
+ /**
+ * Decides if the Java version matches.
+ *
+ * @param versionPrefix the prefix for the java version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getJavaVersionMatches(final String versionPrefix) {
+ return isJavaVersionMatch(JAVA_SPECIFICATION_VERSION, versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the OS name
+ * @param osVersionPrefix the prefix for the version
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOsMatches(final String osNamePrefix, final String osVersionPrefix) {
+ return isOSMatch(OS_NAME, OS_VERSION, osNamePrefix, osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ *
+ * @param osNamePrefix the prefix for the OS name
+ * @return true if matches, or false if not or can't determine
+ */
+ private static boolean getOsMatchesName(final String osNamePrefix) {
+ return isOSNameMatch(OS_NAME, osNamePrefix);
+ }
+
+ /**
+ * Gets the current user directory as a {@link File}.
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getUserDir()
+ * @since 2.1
+ */
+ public static File getUserDir() {
+ return new File(SystemProperties.getUserDir());
+ }
+
+ /**
+ * Gets the current user home directory as a {@link File}.
+ *
+ * @return a directory
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getUserHome()
+ * @since 2.1
+ */
+ public static File getUserHome() {
+ return new File(SystemProperties.getUserHome());
+ }
+
+ /**
+ * Gets the current user name.
+ *
+ * @return a name
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getUserName()
+ * @since 3.10
+ * @deprecated Use {@link SystemProperties#getUserName()}.
+ */
+ @Deprecated
+ public static String getUserName() {
+ return SystemProperties.getUserName();
+ }
+
+ /**
+ * Gets the user name.
+ *
+ * @param defaultValue A default value.
+ * @return a name
+ * @throws SecurityException if a security manager exists and its {@code checkPropertyAccess} method doesn't allow
+ * access to the specified system property.
+ * @see SystemProperties#getUserName()
+ * @since 3.10
+ */
+ public static String getUserName(final String defaultValue) {
+ return System.getProperty(SystemProperties.USER_NAME, defaultValue);
+ }
+
+ /**
+ * Returns whether the {@link #JAVA_AWT_HEADLESS} value is {@code true}.
+ *
+ * @return {@code true} if {@code JAVA_AWT_HEADLESS} is {@code "true"}, {@code false} otherwise.
+ * @see #JAVA_AWT_HEADLESS
+ * @since 2.1
+ * @since Java 1.4
+ */
+ public static boolean isJavaAwtHeadless() {
+ return Boolean.TRUE.toString().equals(JAVA_AWT_HEADLESS);
+ }
+
+ /**
+ * Is the Java version at least the requested version.
+ *
+ * @param requiredVersion the required version, for example 1.31f
+ * @return {@code true} if the actual version is equal or greater than the required version
+ */
+ public static boolean isJavaVersionAtLeast(final JavaVersion requiredVersion) {
+ return JAVA_SPECIFICATION_VERSION_AS_ENUM.atLeast(requiredVersion);
+ }
+
+ /**
+ * Is the Java version at most the requested version.
+ *
+ * <p>
+ * Example input:
+ * </p>
+ *
+ * @param requiredVersion the required version, for example 1.31f
+ * @return {@code true} if the actual version is equal or less than the required version
+ * @since 3.9
+ */
+ public static boolean isJavaVersionAtMost(final JavaVersion requiredVersion) {
+ return JAVA_SPECIFICATION_VERSION_AS_ENUM.atMost(requiredVersion);
+ }
+
+ /**
+ * Decides if the Java version matches.
+ *
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param version the actual Java version
+ * @param versionPrefix the prefix for the expected Java version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isJavaVersionMatch(final String version, final String versionPrefix) {
+ if (version == null) {
+ return false;
+ }
+ return version.startsWith(versionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param osName the actual OS name
+ * @param osVersion the actual OS version
+ * @param osNamePrefix the prefix for the expected OS name
+ * @param osVersionPrefix the prefix for the expected OS version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSMatch(final String osName, final String osVersion, final String osNamePrefix, final String osVersionPrefix) {
+ if (osName == null || osVersion == null) {
+ return false;
+ }
+ return isOSNameMatch(osName, osNamePrefix) && isOSVersionMatch(osVersion, osVersionPrefix);
+ }
+
+ /**
+ * Decides if the operating system matches.
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param osName the actual OS name
+ * @param osNamePrefix the prefix for the expected OS name
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSNameMatch(final String osName, final String osNamePrefix) {
+ if (osName == null) {
+ return false;
+ }
+ return osName.startsWith(osNamePrefix);
+ }
+
+ /**
+ * Decides if the operating system version matches.
+ * <p>
+ * This method is package private instead of private to support unit test invocation.
+ * </p>
+ *
+ * @param osVersion the actual OS version
+ * @param osVersionPrefix the prefix for the expected OS version
+ * @return true if matches, or false if not or can't determine
+ */
+ static boolean isOSVersionMatch(final String osVersion, final String osVersionPrefix) {
+ if (StringUtils.isEmpty(osVersion)) {
+ return false;
+ }
+ // Compare parts of the version string instead of using String.startsWith(String) because otherwise
+ // osVersionPrefix 10.1 would also match osVersion 10.10
+ final String[] versionPrefixParts = osVersionPrefix.split("\\.");
+ final String[] versionParts = osVersion.split("\\.");
+ for (int i = 0; i < Math.min(versionPrefixParts.length, versionParts.length); i++) {
+ if (!versionPrefixParts[i].equals(versionParts[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * SystemUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code SystemUtils.FILE_SEPARATOR}.
+ *
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public SystemUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/ThreadUtils.java b/src/main/java/org/apache/commons/lang3/ThreadUtils.java
new file mode 100644
index 000000000..aa1250b3b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/ThreadUtils.java
@@ -0,0 +1,582 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.time.DurationUtils;
+
+/**
+ * Helpers for {@code java.lang.Thread} and {@code java.lang.ThreadGroup}.
+ *
+ * <p>
+ * #ThreadSafe#
+ * </p>
+ *
+ * @see Thread
+ * @see ThreadGroup
+ * @since 3.5
+ */
+public class ThreadUtils {
+
+ /**
+ * A predicate implementation which always returns true.
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ private static final class AlwaysTruePredicate implements ThreadPredicate, ThreadGroupPredicate {
+
+ private AlwaysTruePredicate() {
+ }
+
+ @Override
+ public boolean test(final Thread thread) {
+ return true;
+ }
+
+ @Override
+ public boolean test(final ThreadGroup threadGroup) {
+ return true;
+ }
+ }
+
+ /**
+ * Used internally, consider private.
+ * <p>
+ * A predicate implementation which matches a thread or thread group name.
+ * </p>
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ public static class NamePredicate implements ThreadPredicate, ThreadGroupPredicate {
+
+ private final String name;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param name thread or thread group name
+ * @throws NullPointerException if the name is {@code null}
+ */
+ public NamePredicate(final String name) {
+ Objects.requireNonNull(name, "name");
+ this.name = name;
+ }
+
+ @Override
+ public boolean test(final Thread thread) {
+ return thread != null && thread.getName().equals(name);
+ }
+
+ @Override
+ public boolean test(final ThreadGroup threadGroup) {
+ return threadGroup != null && threadGroup.getName().equals(name);
+ }
+ }
+
+ /**
+ * A predicate for selecting thread groups.
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ // When breaking BC, replace this with Predicate<ThreadGroup>
+ @FunctionalInterface
+ public interface ThreadGroupPredicate {
+
+ /**
+ * Evaluates this predicate on the given thread group.
+ * @param threadGroup the thread group
+ * @return {@code true} if the threadGroup matches the predicate, otherwise {@code false}
+ */
+ boolean test(ThreadGroup threadGroup);
+ }
+
+ /**
+ * A predicate implementation which matches a thread id.
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ public static class ThreadIdPredicate implements ThreadPredicate {
+
+ private final long threadId;
+
+ /**
+ * Predicate constructor
+ *
+ * @param threadId the threadId to match
+ * @throws IllegalArgumentException if the threadId is zero or negative
+ */
+ public ThreadIdPredicate(final long threadId) {
+ if (threadId <= 0) {
+ throw new IllegalArgumentException("The thread id must be greater than zero");
+ }
+ this.threadId = threadId;
+ }
+
+ @Override
+ public boolean test(final Thread thread) {
+ return thread != null && thread.getId() == threadId;
+ }
+ }
+
+ /**
+ * A predicate for selecting threads.
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ // When breaking BC, replace this with Predicate<Thread>
+ @FunctionalInterface
+ public interface ThreadPredicate {
+
+ /**
+ * Evaluates this predicate on the given thread.
+ * @param thread the thread
+ * @return {@code true} if the thread matches the predicate, otherwise {@code false}
+ */
+ boolean test(Thread thread);
+ }
+
+ /**
+ * Predicate which always returns true.
+ *
+ * @deprecated Use a {@link Predicate}.
+ */
+ @Deprecated
+ public static final AlwaysTruePredicate ALWAYS_TRUE_PREDICATE = new AlwaysTruePredicate();
+
+ private static final Predicate<?> ALWAYS_TRUE = t -> true;
+
+ @SuppressWarnings("unchecked")
+ private static <T> Predicate<T> alwaysTruePredicate() {
+ return (Predicate<T>) ALWAYS_TRUE;
+ }
+
+ /**
+ * Finds the active thread with the specified id.
+ *
+ * @param threadId The thread id
+ * @return The thread with the specified id or {@code null} if no such thread exists
+ * @throws IllegalArgumentException if the specified id is zero or negative
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Thread findThreadById(final long threadId) {
+ if (threadId <= 0) {
+ throw new IllegalArgumentException("The thread id must be greater than zero");
+ }
+ final Collection<Thread> result = findThreads((Predicate<Thread>) t -> t != null && t.getId() == threadId);
+ return result.isEmpty() ? null : result.iterator().next();
+ }
+
+ /**
+ * Finds the active thread with the specified id if it belongs to a thread group with the specified group name.
+ *
+ * @param threadId The thread id
+ * @param threadGroupName The thread group name
+ * @return The threads which belongs to a thread group with the specified group name and the thread's id match the specified id.
+ * {@code null} is returned if no such thread exists
+ * @throws NullPointerException if the group name is null
+ * @throws IllegalArgumentException if the specified id is zero or negative
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Thread findThreadById(final long threadId, final String threadGroupName) {
+ Objects.requireNonNull(threadGroupName, "threadGroupName");
+ final Thread thread = findThreadById(threadId);
+ if (thread != null && thread.getThreadGroup() != null && thread.getThreadGroup().getName().equals(threadGroupName)) {
+ return thread;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the active thread with the specified id if it belongs to the specified thread group.
+ *
+ * @param threadId The thread id
+ * @param threadGroup The thread group
+ * @return The thread which belongs to a specified thread group and the thread's id match the specified id.
+ * {@code null} is returned if no such thread exists
+ * @throws NullPointerException if {@code threadGroup == null}
+ * @throws IllegalArgumentException if the specified id is zero or negative
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Thread findThreadById(final long threadId, final ThreadGroup threadGroup) {
+ Objects.requireNonNull(threadGroup, "threadGroup");
+ final Thread thread = findThreadById(threadId);
+ if (thread != null && threadGroup.equals(thread.getThreadGroup())) {
+ return thread;
+ }
+ return null;
+ }
+
+ /**
+ * Finds all active thread groups which match the given predicate.
+ *
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active thread groups matching the given predicate
+ * @throws NullPointerException if the predicate is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @since 3.13.0
+ */
+ public static Collection<ThreadGroup> findThreadGroups(final Predicate<ThreadGroup> predicate) {
+ return findThreadGroups(getSystemThreadGroup(), true, predicate);
+ }
+
+ /**
+ * Finds all active thread groups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups).
+ *
+ * @param threadGroup the thread group
+ * @param recurse if {@code true} then evaluate the predicate recursively on all thread groups in all subgroups of the given group
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active thread groups which match the given predicate and which is a subgroup of the given thread group
+ * @throws NullPointerException if the given group or predicate is null
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @since 3.13.0
+ */
+ public static Collection<ThreadGroup> findThreadGroups(final ThreadGroup threadGroup, final boolean recurse, final Predicate<ThreadGroup> predicate) {
+ Objects.requireNonNull(threadGroup, "threadGroup");
+ Objects.requireNonNull(predicate, "predicate");
+
+ int count = threadGroup.activeGroupCount();
+ ThreadGroup[] threadGroups;
+ do {
+ threadGroups = new ThreadGroup[count + count / 2 + 1]; //slightly grow the array size
+ count = threadGroup.enumerate(threadGroups, recurse);
+ //return value of enumerate() must be strictly less than the array size according to Javadoc
+ } while (count >= threadGroups.length);
+ return Collections.unmodifiableCollection(Stream.of(threadGroups).filter(predicate).collect(Collectors.toList()));
+ }
+
+ /**
+ * Finds all active thread groups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups).
+ *
+ * @param threadGroup the thread group
+ * @param recurse if {@code true} then evaluate the predicate recursively on all thread groups in all subgroups of the given group
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active thread groups which match the given predicate and which is a subgroup of the given thread group
+ * @throws NullPointerException if the given group or predicate is null
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @deprecated Use {@link #findThreadGroups(ThreadGroup, boolean, Predicate)}.
+ */
+ @Deprecated
+ public static Collection<ThreadGroup> findThreadGroups(final ThreadGroup threadGroup, final boolean recurse, final ThreadGroupPredicate predicate) {
+ return findThreadGroups(threadGroup, recurse, (Predicate<ThreadGroup>) predicate::test);
+ }
+
+ /**
+ * Finds all active thread groups which match the given predicate.
+ *
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active thread groups matching the given predicate
+ * @throws NullPointerException if the predicate is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @deprecated Use {@link #findThreadGroups(Predicate)}.
+ */
+ @Deprecated
+ public static Collection<ThreadGroup> findThreadGroups(final ThreadGroupPredicate predicate) {
+ return findThreadGroups(getSystemThreadGroup(), true, predicate);
+ }
+
+ /**
+ * Finds active thread groups with the specified group name.
+ *
+ * @param threadGroupName The thread group name
+ * @return the thread groups with the specified group name or an empty collection if no such thread group exists. The collection returned is always unmodifiable.
+ * @throws NullPointerException if group name is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<ThreadGroup> findThreadGroupsByName(final String threadGroupName) {
+ return findThreadGroups(predicateThreadGroup(threadGroupName));
+ }
+
+ /**
+ * Finds all active threads which match the given predicate.
+ *
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active threads matching the given predicate
+ *
+ * @throws NullPointerException if the predicate is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @since 3.13.0
+ */
+ public static Collection<Thread> findThreads(final Predicate<Thread> predicate) {
+ return findThreads(getSystemThreadGroup(), true, predicate);
+ }
+
+ /**
+ * Finds all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups).
+ *
+ * @param threadGroup the thread group
+ * @param recurse if {@code true} then evaluate the predicate recursively on all threads in all subgroups of the given group
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active threads which match the given predicate and which belongs to the given thread group
+ * @throws NullPointerException if the given group or predicate is null
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @since 3.13.0
+ */
+ public static Collection<Thread> findThreads(final ThreadGroup threadGroup, final boolean recurse, final Predicate<Thread> predicate) {
+ Objects.requireNonNull(threadGroup, "The group must not be null");
+ Objects.requireNonNull(predicate, "The predicate must not be null");
+ int count = threadGroup.activeCount();
+ Thread[] threads;
+ do {
+ threads = new Thread[count + count / 2 + 1]; //slightly grow the array size
+ count = threadGroup.enumerate(threads, recurse);
+ //return value of enumerate() must be strictly less than the array size according to javadoc
+ } while (count >= threads.length);
+ return Collections.unmodifiableCollection(Stream.of(threads).filter(predicate).collect(Collectors.toList()));
+ }
+
+ /**
+ * Finds all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups).
+ *
+ * @param threadGroup the thread group
+ * @param recurse if {@code true} then evaluate the predicate recursively on all threads in all subgroups of the given group
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active threads which match the given predicate and which belongs to the given thread group
+ * @throws NullPointerException if the given group or predicate is null
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @deprecated Use {@link #findThreads(ThreadGroup, boolean, Predicate)}.
+ */
+ @Deprecated
+ public static Collection<Thread> findThreads(final ThreadGroup threadGroup, final boolean recurse, final ThreadPredicate predicate) {
+ return findThreads(threadGroup, recurse, (Predicate<Thread>) predicate::test);
+ }
+
+ /**
+ * Finds all active threads which match the given predicate.
+ *
+ * @param predicate the predicate
+ * @return An unmodifiable {@link Collection} of active threads matching the given predicate
+ *
+ * @throws NullPointerException if the predicate is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ * @deprecated Use {@link #findThreads(Predicate)}.
+ */
+ @Deprecated
+ public static Collection<Thread> findThreads(final ThreadPredicate predicate) {
+ return findThreads(getSystemThreadGroup(), true, predicate);
+ }
+
+ /**
+ * Finds active threads with the specified name.
+ *
+ * @param threadName The thread name
+ * @return The threads with the specified name or an empty collection if no such thread exists. The collection returned is always unmodifiable.
+ * @throws NullPointerException if the specified name is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<Thread> findThreadsByName(final String threadName) {
+ return findThreads(predicateThread(threadName));
+ }
+
+ /**
+ * Finds active threads with the specified name if they belong to a thread group with the specified group name.
+ *
+ * @param threadName The thread name
+ * @param threadGroupName The thread group name
+ * @return The threads which belongs to a thread group with the specified group name and the thread's name match the specified name,
+ * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
+ * @throws NullPointerException if the specified thread name or group name is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<Thread> findThreadsByName(final String threadName, final String threadGroupName) {
+ Objects.requireNonNull(threadName, "threadName");
+ Objects.requireNonNull(threadGroupName, "threadGroupName");
+ return Collections.unmodifiableCollection(findThreadGroups(predicateThreadGroup(threadGroupName)).stream()
+ .flatMap(group -> findThreads(group, false, predicateThread(threadName)).stream()).collect(Collectors.toList()));
+ }
+
+ /**
+ * Finds active threads with the specified name if they belong to a specified thread group.
+ *
+ * @param threadName The thread name
+ * @param threadGroup The thread group
+ * @return The threads which belongs to a thread group and the thread's name match the specified name,
+ * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
+ * @throws NullPointerException if the specified thread name or group is null
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<Thread> findThreadsByName(final String threadName, final ThreadGroup threadGroup) {
+ return findThreads(threadGroup, false, predicateThread(threadName));
+ }
+
+ /**
+ * Gets all active thread groups excluding the system thread group (A thread group is active if it has been not destroyed).
+ *
+ * @return all thread groups excluding the system thread group. The collection returned is always unmodifiable.
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<ThreadGroup> getAllThreadGroups() {
+ return findThreadGroups(alwaysTruePredicate());
+ }
+
+ /**
+ * Gets all active threads (A thread is active if it has been started and has not yet died).
+ *
+ * @return all active threads. The collection returned is always unmodifiable.
+ * @throws SecurityException
+ * if the current thread cannot access the system thread group
+ *
+ * @throws SecurityException if the current thread cannot modify
+ * thread groups from this thread's thread group up to the system thread group
+ */
+ public static Collection<Thread> getAllThreads() {
+ return findThreads(alwaysTruePredicate());
+ }
+
+ /**
+ * Gets the system thread group (sometimes also referred as "root thread group").
+ * <p>
+ * This method returns null if this thread has died (been stopped).
+ * </p>
+ *
+ * @return the system thread group
+ * @throws SecurityException if the current thread cannot modify thread groups from this thread's thread group up to the
+ * system thread group
+ */
+ public static ThreadGroup getSystemThreadGroup() {
+ ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
+ while (threadGroup != null && threadGroup.getParent() != null) {
+ threadGroup = threadGroup.getParent();
+ }
+ return threadGroup;
+ }
+
+ /**
+ * Waits for the given thread to die for the given duration. Implemented using {@link Thread#join(long, int)}.
+ *
+ * @param thread The thread to join.
+ * @param duration How long to wait.
+ * @throws InterruptedException if any thread has interrupted the current thread.
+ * @see Thread#join(long, int)
+ * @since 3.12.0
+ */
+ public static void join(final Thread thread, final Duration duration) throws InterruptedException {
+ DurationUtils.accept(thread::join, duration);
+ }
+
+ private static <T> Predicate<T> namePredicate(final String name, final Function<T, String> nameGetter) {
+ return (Predicate<T>) t -> t != null && Objects.equals(nameGetter.apply(t), Objects.requireNonNull(name));
+ }
+
+ private static Predicate<Thread> predicateThread(final String threadName) {
+ return namePredicate(threadName, Thread::getName);
+ }
+
+ private static Predicate<ThreadGroup> predicateThreadGroup(final String threadGroupName) {
+ return namePredicate(threadGroupName, ThreadGroup::getName);
+ }
+
+ /**
+ * Sleeps the current thread for the given duration. Implemented using {@link Thread#sleep(long, int)}.
+ *
+ * @param duration How long to sleep.
+ * @throws InterruptedException if any thread has interrupted the current thread.
+ * @see Thread#sleep(long, int)
+ * @since 3.12.0
+ */
+ public static void sleep(final Duration duration) throws InterruptedException {
+ DurationUtils.accept(Thread::sleep, duration);
+ }
+
+ /**
+ * Sleeps for the given duration while ignoring {@link InterruptedException}.
+ * <p>
+ * The sleep duration may be shorter than duration if we catch a {@link InterruptedException}.
+ * </p>
+ *
+ * @param duration the length of time to sleep.
+ * @since 3.13.0
+ */
+ public static void sleepQuietly(final Duration duration) {
+ try {
+ sleep(duration);
+ } catch (final InterruptedException e) {
+ // be quiet.
+ }
+ }
+
+ /**
+ * ThreadUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
+ * {@code ThreadUtils.getAllThreads()}
+ *
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public ThreadUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/Validate.java b/src/main/java/org/apache/commons/lang3/Validate.java
new file mode 100644
index 000000000..c0e37d7b0
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Validate.java
@@ -0,0 +1,1255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+
+/**
+ * This class assists in validating arguments. The validation methods are
+ * based along the following principles:
+ * <ul>
+ * <li>An invalid {@code null} argument causes a {@link NullPointerException}.</li>
+ * <li>A non-{@code null} argument causes an {@link IllegalArgumentException}.</li>
+ * <li>An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.</li>
+ * </ul>
+ *
+ * <p>All exceptions messages are
+ * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax">format strings</a>
+ * as defined by the Java platform. For example:
+ *
+ * <pre>
+ * Validate.isTrue(i &gt; 0, "The value must be greater than zero: %d", i);
+ * Validate.notNull(surname, "The surname must not be %s", null);
+ * </pre>
+ *
+ * <p>#ThreadSafe#</p>
+ * @see String#format(String, Object...)
+ * @since 2.0
+ */
+public class Validate {
+
+ private static final String DEFAULT_NOT_NAN_EX_MESSAGE =
+ "The validated value is not a number";
+ private static final String DEFAULT_FINITE_EX_MESSAGE =
+ "The value is invalid: %f";
+ private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified exclusive range of %s to %s";
+ private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE =
+ "The value %s is not in the specified inclusive range of %s to %s";
+ private static final String DEFAULT_MATCHES_PATTERN_EX = "The string %s does not match the pattern %s";
+ private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null";
+ private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE =
+ "The validated array contains null element at index: %d";
+ private static final String DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE =
+ "The validated collection contains null element at index: %d";
+ private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank";
+ private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty";
+ private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence is empty";
+ private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty";
+ private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty";
+ private static final String DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE = "The validated array index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE =
+ "The validated character sequence index is invalid: %d";
+ private static final String DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE =
+ "The validated collection index is invalid: %d";
+ private static final String DEFAULT_VALID_STATE_EX_MESSAGE = "The validated state is false";
+ private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "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.
+ *
+ * <pre>Validate.isTrue(i &gt; 0.0, "The value must be greater than zero: &#37;d", i);</pre>
+ *
+ * <p>For performance reasons, the long value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.</p>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(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.
+ *
+ * <pre>Validate.isTrue(d &gt; 0.0, "The value must be greater than zero: &#37;s", d);</pre>
+ *
+ * <p>For performance reasons, the double value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.</p>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(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.
+ *
+ * <pre>
+ * Validate.isTrue(i &gt;= min &amp;&amp; i &lt;= max, "The value must be between &#37;d and &#37;d", min, max);</pre>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean)
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ */
+ public static void isTrue(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.
+ *
+ * <pre>
+ * Validate.isTrue(i &gt; 0);
+ * Validate.isTrue(myObject.isOk());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated expression is
+ * false&quot;.</p>
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalArgumentException if expression is {@code false}
+ * @see #isTrue(boolean, String, long)
+ * @see #isTrue(boolean, String, double)
+ * @see #isTrue(boolean, String, Object...)
+ */
+ public static void isTrue(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.
+ *
+ * <pre>Validate.notNull(myObject, "The object must not be null");</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object is
+ * null&quot;.
+ *
+ * @param <T> the object type
+ * @param object the object to check
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see #notNull(Object, String, Object...)
+ * @deprecated Use {@link Objects#requireNonNull(Object)}.
+ */
+ @Deprecated
+ public static <T> 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.
+ *
+ * <pre>Validate.notNull(myObject, "The object must not be null");</pre>
+ *
+ * @param <T> the object type
+ * @param object the object to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ * @see Objects#requireNonNull(Object)
+ */
+ public static <T> T notNull(final T object, final String message, final Object... values) {
+ return Objects.requireNonNull(object, toSupplier(message, values));
+ }
+
+ private static Supplier<String> toSupplier(final String message, final Object... values) {
+ return () -> getMessage(message, values);
+ }
+
+ /**
+ * <p>Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ * <pre>Validate.notEmpty(myArray, "The array must not be empty");</pre>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T> T[] notEmpty(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;
+ }
+
+ /**
+ * <p>Validate that the specified argument array is neither {@code null}
+ * nor a length of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myArray);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated array is
+ * empty&quot;.
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if the array is empty
+ * @see #notEmpty(Object[], String, Object...)
+ */
+ public static <T> T[] notEmpty(final T[] array) {
+ return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE);
+ }
+
+ /**
+ * <p>Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ * <pre>Validate.notEmpty(myCollection, "The collection must not be empty");</pre>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T extends Collection<?>> T notEmpty(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;
+ }
+
+ /**
+ * <p>Validate that the specified argument collection is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myCollection);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated collection is
+ * empty&quot;.
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @return the validated collection (never {@code null} method for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IllegalArgumentException if the collection is empty
+ * @see #notEmpty(Collection, String, Object...)
+ */
+ public static <T extends Collection<?>> T notEmpty(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.
+ *
+ * <pre>Validate.notEmpty(myMap, "The map must not be empty");</pre>
+ *
+ * @param <T> the map type
+ * @param map the map to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Object[])
+ */
+ public static <T extends Map<?, ?>> T notEmpty(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;
+ }
+
+ /**
+ * <p>Validate that the specified argument map is neither {@code null}
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ * <pre>Validate.notEmpty(myMap);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated map is
+ * empty&quot;.
+ *
+ * @param <T> the map type
+ * @param map the map to check, validated not null by this method
+ * @return the validated map (never {@code null} method for chaining)
+ * @throws NullPointerException if the map is {@code null}
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(Map, String, Object...)
+ */
+ public static <T extends Map<?, ?>> T notEmpty(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.
+ *
+ * <pre>Validate.notEmpty(myString, "The string must not be empty");</pre>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence)
+ */
+ public static <T extends CharSequence> T notEmpty(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;
+ }
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ * <pre>Validate.notEmpty(myString);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated
+ * character sequence is empty&quot;.
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ * @see #notEmpty(CharSequence, String, Object...)
+ */
+ public static <T extends CharSequence> T notEmpty(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.
+ *
+ * <pre>Validate.notBlank(myString, "The string must not be blank");</pre>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence)
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T notBlank(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;
+ }
+
+ /**
+ * <p>Validate that the specified argument character sequence is
+ * neither {@code null}, a length of zero (no characters), empty
+ * nor whitespace; otherwise throwing an exception.
+ *
+ * <pre>Validate.notBlank(myString);</pre>
+ *
+ * <p>The message in the exception is &quot;The validated character
+ * sequence is blank&quot;.
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is blank
+ * @see #notBlank(CharSequence, String, Object...)
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T notBlank(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.
+ *
+ * <pre>Validate.noNullElements(myArray, "The array contain null at position %d");</pre>
+ *
+ * <p>If the array is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.
+ *
+ * <p>If the array has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[])
+ */
+ public static <T> T[] noNullElements(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.
+ *
+ * <pre>Validate.noNullElements(myArray);</pre>
+ *
+ * <p>If the array is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the array has a {@code null} element, then the message in the
+ * exception is &quot;The validated array contains null element at index:
+ * &quot; followed by the index.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @return the validated array (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Object[], String, Object...)
+ */
+ public static <T> T[] noNullElements(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.
+ *
+ * <pre>Validate.noNullElements(myCollection, "The collection contains null at position %d");</pre>
+ *
+ * <p>If the iterable is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.
+ *
+ * <p>If the iterable has a {@code null} element, then the iteration
+ * index of the invalid element is appended to the {@code values}
+ * argument.</p>
+ *
+ * @param <T> the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable)
+ */
+ public static <T extends Iterable<?>> T noNullElements(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.
+ *
+ * <pre>Validate.noNullElements(myCollection);</pre>
+ *
+ * <p>If the iterable is {@code null}, then the message in the exception
+ * is &quot;The validated object is null&quot;.
+ *
+ * <p>If the array has a {@code null} element, then the message in the
+ * exception is &quot;The validated iterable contains null element at index:
+ * &quot; followed by the index.</p>
+ *
+ * @param <T> the iterable type
+ * @param iterable the iterable to check, validated not null by this method
+ * @return the validated iterable (never {@code null} method for chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IllegalArgumentException if an element is {@code null}
+ * @see #noNullElements(Iterable, String, Object...)
+ */
+ public static <T extends Iterable<?>> T noNullElements(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.
+ *
+ * <pre>Validate.validIndex(myArray, 2, "The array index is invalid: ");</pre>
+ *
+ * <p>If the array is {@code null}, then the message of the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int)
+ * @since 3.0
+ */
+ public static <T> T[] validIndex(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.
+ *
+ * <pre>Validate.validIndex(myArray, 2);</pre>
+ *
+ * <p>If the array is {@code null}, then the message of the exception
+ * is &quot;The validated object is null&quot;.</p>
+ *
+ * <p>If the index is invalid, then the message of the exception is
+ * &quot;The validated array index is invalid: &quot; followed by the
+ * index.</p>
+ *
+ * @param <T> the array type
+ * @param array the array to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated array (never {@code null} for method chaining)
+ * @throws NullPointerException if the array is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Object[], int, String, Object...)
+ * @since 3.0
+ */
+ public static <T> T[] validIndex(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.
+ *
+ * <pre>Validate.validIndex(myCollection, 2, "The collection index is invalid: ");</pre>
+ *
+ * <p>If the collection is {@code null}, then the message of the
+ * exception is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated collection (never {@code null} for chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int)
+ * @since 3.0
+ */
+ public static <T extends Collection<?>> T validIndex(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.
+ *
+ * <pre>Validate.validIndex(myCollection, 2);</pre>
+ *
+ * <p>If the index is invalid, then the message of the exception
+ * is &quot;The validated collection index is invalid: &quot;
+ * followed by the index.</p>
+ *
+ * @param <T> the collection type
+ * @param collection the collection to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated collection (never {@code null} for method chaining)
+ * @throws NullPointerException if the collection is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(Collection, int, String, Object...)
+ * @since 3.0
+ */
+ public static <T extends Collection<?>> T validIndex(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.
+ *
+ * <pre>Validate.validIndex(myStr, 2, "The string index is invalid: ");</pre>
+ *
+ * <p>If the character sequence is {@code null}, then the message
+ * of the exception is &quot;The validated object is null&quot;.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int)
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T validIndex(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.
+ *
+ * <pre>Validate.validIndex(myStr, 2);</pre>
+ *
+ * <p>If the character sequence is {@code null}, then the message
+ * of the exception is &quot;The validated object is
+ * null&quot;.</p>
+ *
+ * <p>If the index is invalid, then the message of the exception
+ * is &quot;The validated character sequence index is invalid: &quot;
+ * followed by the index.</p>
+ *
+ * @param <T> the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param index the index to check
+ * @return the validated character sequence (never {@code null} for method chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IndexOutOfBoundsException if the index is invalid
+ * @see #validIndex(CharSequence, int, String, Object...)
+ * @since 3.0
+ */
+ public static <T extends CharSequence> T validIndex(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.
+ *
+ * <pre>
+ * Validate.validState(field &gt; 0);
+ * Validate.validState(this.isOk());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated state is
+ * false&quot;.</p>
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean, String, Object...)
+ * @since 3.0
+ */
+ public static void validState(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.
+ *
+ * <pre>Validate.validState(this.isOk(), "The state is not OK: %s", myObject);</pre>
+ *
+ * @param expression the boolean expression to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalStateException if expression is {@code false}
+ * @see #validState(boolean)
+ * @since 3.0
+ */
+ public static void validState(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.
+ *
+ * <pre>Validate.matchesPattern("hi", "[a-z]*");</pre>
+ *
+ * <p>The syntax of the pattern is the one used in the {@link Pattern} class.</p>
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String, String, Object...)
+ * @since 3.0
+ */
+ public static void matchesPattern(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.
+ *
+ * <pre>Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");</pre>
+ *
+ * <p>The syntax of the pattern is the one used in the {@link Pattern} class.</p>
+ *
+ * @param input the character sequence to validate, not null
+ * @param pattern the regular expression pattern, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the character sequence does not match the pattern
+ * @see #matchesPattern(CharSequence, String)
+ * @since 3.0
+ */
+ public static void matchesPattern(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.
+ *
+ * <pre>Validate.notNaN(myDouble);</pre>
+ *
+ * <p>The message of the exception is &quot;The validated value is not a
+ * number&quot;.</p>
+ *
+ * @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.
+ *
+ * <pre>Validate.notNaN(myDouble, "The value must be a number");</pre>
+ *
+ * @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.
+ *
+ * <pre>Validate.finite(myDouble);</pre>
+ *
+ * <p>The message of the exception is &quot;The value is invalid: %f&quot;.</p>
+ *
+ * @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.
+ *
+ * <pre>Validate.finite(myDouble, "The argument must contain a numeric value");</pre>
+ *
+ * @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.
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1);</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls outside the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable, String, Object...)
+ * @since 3.0
+ */
+ public static <T> void inclusiveBetween(final T start, final T end, final Comparable<T> 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.
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the inclusive start value, not null
+ * @param end the inclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls outside the boundaries
+ * @see #inclusiveBetween(Object, Object, Comparable)
+ * @since 3.0
+ */
+ public static <T> void inclusiveBetween(final T start, final T end, final Comparable<T> 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.
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1);</pre>
+ *
+ * @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 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_INCLUSIVE_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
+ * specified message.
+ *
+ * <pre>Validate.inclusiveBetween(0, 2, 1, "Not in range");</pre>
+ *
+ * @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 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) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified primitive value falls between the two
+ * inclusive values specified; otherwise, throws an exception.
+ *
+ * <pre>Validate.inclusiveBetween(0.1, 2.1, 1.1);</pre>
+ *
+ * @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 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_INCLUSIVE_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
+ * specified message.
+ *
+ * <pre>Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");</pre>
+ *
+ * @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 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) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception.
+ *
+ * <pre>Validate.exclusiveBetween(0, 2, 1);</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @throws IllegalArgumentException if the value falls outside the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable, String, Object...)
+ * @since 3.0
+ */
+ public static <T> void exclusiveBetween(final T start, final T end, final Comparable<T> value) {
+ // TODO when breaking BC, consider returning value
+ if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) {
+ throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end));
+ }
+ }
+
+ /**
+ * Validate that the specified argument object fall between the two
+ * exclusive values specified; otherwise, throws an exception with the
+ * specified message.
+ *
+ * <pre>Validate.exclusiveBetween(0, 2, 1, "Not in boundaries");</pre>
+ *
+ * @param <T> the type of the argument object
+ * @param start the exclusive start value, not null
+ * @param end the exclusive end value, not null
+ * @param value the object to validate, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if the value falls outside the boundaries
+ * @see #exclusiveBetween(Object, Object, Comparable)
+ * @since 3.0
+ */
+ public static <T> void exclusiveBetween(final T start, final T end, final Comparable<T> 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
+ * exclusive values specified; otherwise, throws an exception.
+ *
+ * <pre>Validate.exclusiveBetween(0, 2, 1);</pre>
+ *
+ * @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
+ * @since 3.3
+ */
+ @SuppressWarnings("boxing")
+ 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_EXCLUSIVE_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
+ * specified message.
+ *
+ * <pre>Validate.exclusiveBetween(0, 2, 1, "Not in range");</pre>
+ *
+ * @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 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) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified primitive value falls between the two
+ * exclusive values specified; otherwise, throws an exception.
+ *
+ * <pre>Validate.exclusiveBetween(0.1, 2.1, 1.1);</pre>
+ *
+ * @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
+ * @since 3.3
+ */
+ @SuppressWarnings("boxing")
+ 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_EXCLUSIVE_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
+ * specified message.
+ *
+ * <pre>Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");</pre>
+ *
+ * @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 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) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validates that the argument is an instance of the specified class, if not throws an exception.
+ *
+ * <p>This method is useful when validating according to an arbitrary class</p>
+ *
+ * <pre>Validate.isInstanceOf(OkClass.class, object);</pre>
+ *
+ * <p>The message of the exception is &quot;Expected type: {type}, actual: {obj_type}&quot;</p>
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object, String, Object...)
+ * @since 3.0
+ */
+ public static void isInstanceOf(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
+ *
+ * <pre>Validate.isInstanceOf(OkClass.class, object, "Wrong class, object is of class %s",
+ * object.getClass().getName());</pre>
+ *
+ * @param type the class the object must be validated against, not null
+ * @param obj the object to check, null throws an exception
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument is not of specified class
+ * @see #isInstanceOf(Class, Object)
+ * @since 3.0
+ */
+ public static void isInstanceOf(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));
+ }
+ }
+
+ /**
+ * Validates that the argument can be converted to the specified class, if not, throws an exception.
+ *
+ * <p>This method is useful when validating that there will be no casting errors.</p>
+ *
+ * <pre>Validate.isAssignableFrom(SuperClass.class, object.getClass());</pre>
+ *
+ * <p>The message format of the exception is &quot;Cannot assign {type} to {superType}&quot;</p>
+ *
+ * @param superType the class must be validated against, not null
+ * @param type the class to check, not null
+ * @throws IllegalArgumentException if type argument is not assignable to the specified superType
+ * @see #isAssignableFrom(Class, Class, String, Object...)
+ * @since 3.0
+ */
+ public static void isAssignableFrom(final Class<?> superType, final Class<?> type) {
+ // TODO when breaking BC, consider returning type
+ if (type == null || superType == null || !superType.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(
+ String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, ClassUtils.getName(type, "null type"), ClassUtils.getName(superType, "null type")));
+ }
+ }
+
+ /**
+ * Validates that the argument can be converted to the specified class, if not throws an exception.
+ *
+ * <p>This method is useful when validating if there will be no casting errors.</p>
+ *
+ * <pre>Validate.isAssignableFrom(SuperClass.class, object.getClass());</pre>
+ *
+ * <p>The message of the exception is &quot;The validated object can not be converted to the&quot;
+ * followed by the name of the class and &quot;class&quot;</p>
+ *
+ * @param superType the class must be validated against, not null
+ * @param type the class to check, not null
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @throws IllegalArgumentException if argument can not be converted to the specified class
+ * @see #isAssignableFrom(Class, Class)
+ */
+ public static void isAssignableFrom(final Class<?> superType, final Class<?> type, final String message, final Object... values) {
+ // TODO when breaking BC, consider returning type
+ if (!superType.isAssignableFrom(type)) {
+ 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);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/arch/Processor.java b/src/main/java/org/apache/commons/lang3/arch/Processor.java
new file mode 100644
index 000000000..0d3f835fa
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/arch/Processor.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.arch;
+
+/**
+ * The {@link Processor} represents a microprocessor and defines
+ * some properties like architecture and type of the microprocessor.
+ *
+ * @since 3.6
+ */
+public class Processor {
+
+ /**
+ * The {@link Arch} enum defines the architecture of
+ * a microprocessor. The architecture represents the bit value
+ * of the microprocessor.
+ * The following architectures are defined:
+ * <ul>
+ * <li>32-bit</li>
+ * <li>64-bit</li>
+ * <li>Unknown</li>
+ * </ul>
+ */
+ public enum Arch {
+
+ /**
+ * A 32-bit processor architecture.
+ */
+ BIT_32("32-bit"),
+
+ /**
+ * A 64-bit processor architecture.
+ */
+ BIT_64("64-bit"),
+
+ /**
+ * An unknown-bit processor architecture.
+ */
+ UNKNOWN("Unknown");
+
+ /**
+ * A label suitable for display.
+ */
+ private final String label;
+
+ Arch(final String label) {
+ this.label = label;
+ }
+
+ /**
+ * Gets the label suitable for display.
+ *
+ * @return the label.
+ */
+ public String getLabel() {
+ return label;
+ }
+ }
+
+ /**
+ * The {@link Type} enum defines types of a microprocessor.
+ * The following types are defined:
+ * <ul>
+ * <li>AArch64</li>
+ * <li>x86</li>
+ * <li>ia64</li>
+ * <li>PPC</li>
+ * <li>Unknown</li>
+ * </ul>
+ */
+ public enum Type {
+
+ /**
+ * ARM 64-bit.
+ *
+ * @since 3.13.0
+ */
+ AARCH_64("AArch64"),
+
+ /**
+ * Intel x86 series of instruction set architectures.
+ */
+ X86("x86"),
+
+ /**
+ * Intel Itanium 64-bit architecture.
+ */
+ IA_64("IA-64"),
+
+ /**
+ * Apple–IBM–Motorola PowerPC architecture.
+ */
+ PPC("PPC"),
+
+ /**
+ * Unknown architecture.
+ */
+ UNKNOWN("Unknown");
+
+ /**
+ * A label suitable for display.
+ */
+ private final String label;
+
+ Type(final String label) {
+ this.label = label;
+ }
+
+ /**
+ * Gets the label suitable for display.
+ *
+ * @return the label.
+ * @since 3.13.0
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ }
+
+ private final Arch arch;
+ private final Type type;
+
+ /**
+ * Constructs a {@link Processor} object with the given
+ * parameters.
+ *
+ * @param arch The processor architecture.
+ * @param type The processor type.
+ */
+ public Processor(final Arch arch, final Type type) {
+ this.arch = arch;
+ this.type = type;
+ }
+
+ /**
+ * Gets the processor architecture as an {@link Arch} enum.
+ * The processor architecture defines, if the processor has
+ * a 32 or 64 bit architecture.
+ *
+ * @return A {@link Arch} enum.
+ */
+ public Arch getArch() {
+ return arch;
+ }
+
+ /**
+ * Gets the processor type as {@link Type} enum.
+ * The processor type defines, if the processor is for example
+ * an x86 or PPA.
+ *
+ * @return A {@link Type} enum.
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Tests if {@link Processor} is 32 bit.
+ *
+ * @return {@code true}, if {@link Processor} is {@link Arch#BIT_32}, else {@code false}.
+ */
+ public boolean is32Bit() {
+ return Arch.BIT_32 == arch;
+ }
+
+ /**
+ * Tests if {@link Processor} is 64 bit.
+ *
+ * @return {@code true}, if {@link Processor} is {@link Arch#BIT_64}, else {@code false}.
+ */
+ public boolean is64Bit() {
+ return Arch.BIT_64 == arch;
+ }
+
+ /**
+ * Tests if {@link Processor} is type of Aarch64.
+ *
+ * @return {@code true}, if {@link Processor} is {@link Type#X86}, else {@code false}.
+ *
+ * @since 3.13.0
+ */
+ public boolean isAarch64() {
+ return Type.AARCH_64 == type;
+ }
+
+ /**
+ * Tests if {@link Processor} is type of Intel Itanium.
+ *
+ * @return {@code true}. if {@link Processor} is {@link Type#IA_64}, else {@code false}.
+ */
+ public boolean isIA64() {
+ return Type.IA_64 == type;
+ }
+
+ /**
+ * Tests if {@link Processor} is type of Power PC.
+ *
+ * @return {@code true}. if {@link Processor} is {@link Type#PPC}, else {@code false}.
+ */
+ public boolean isPPC() {
+ return Type.PPC == type;
+ }
+
+ /**
+ * Tests if {@link Processor} is type of x86.
+ *
+ * @return {@code true}, if {@link Processor} is {@link Type#X86}, else {@code false}.
+ */
+ public boolean isX86() {
+ return Type.X86 == type;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(type.getLabel()).append(' ').append(arch.getLabel());
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/arch/package-info.java b/src/main/java/org/apache/commons/lang3/arch/package-info.java
new file mode 100644
index 000000000..f2561907a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/arch/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides classes to work with the values of the os.arch system property.
+ * @since 3.6
+ */
+package org.apache.commons.lang3.arch;
diff --git a/src/main/java/org/apache/commons/lang3/builder/Builder.java b/src/main/java/org/apache/commons/lang3/builder/Builder.java
new file mode 100644
index 000000000..5146830f3
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/Builder.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+/**
+ * The Builder interface is designed to designate a class as a <em>builder</em>
+ * object in the Builder design pattern. Builders are capable of creating and
+ * configuring objects or results that normally take multiple steps to construct
+ * or are very complex to derive.
+ *
+ * <p>
+ * The builder interface defines a single method, {@link #build()}, that
+ * classes must implement. The result of this method should be the final
+ * configured object or result after all building operations are performed.
+ * </p>
+ *
+ * <p>
+ * It is a recommended practice that the methods supplied to configure the
+ * object or result being built return a reference to {@code this} so that
+ * method calls can be chained together.
+ * </p>
+ *
+ * <p>
+ * Example Builder:
+ * <pre><code>
+ * class FontBuilder implements Builder&lt;Font&gt; {
+ * private Font font;
+ *
+ * public FontBuilder(String fontName) {
+ * this.font = new Font(fontName, Font.PLAIN, 12);
+ * }
+ *
+ * public FontBuilder bold() {
+ * this.font = this.font.deriveFont(Font.BOLD);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * public FontBuilder size(float pointSize) {
+ * this.font = this.font.deriveFont(pointSize);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * // Other Font construction methods
+ *
+ * public Font build() {
+ * return this.font;
+ * }
+ * }
+ * </code></pre>
+ *
+ * Example Builder Usage:
+ * <pre><code>
+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ * .size(14.0f)
+ * .build();
+ * </code></pre>
+ *
+ * @param <T> the type of object that the builder will construct or compute.
+ *
+ * @since 3.0
+ */
+@FunctionalInterface
+public interface Builder<T> {
+
+ /**
+ * Returns a reference to the object being constructed or result being
+ * calculated by the builder.
+ *
+ * @return the object constructed or result calculated by the builder.
+ */
+ T build();
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java b/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java
new file mode 100644
index 000000000..844ec86be
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java
@@ -0,0 +1,1026 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Assists in implementing {@link java.lang.Comparable#compareTo(Object)} methods.
+ *
+ * <p>It is consistent with {@code equals(Object)} and
+ * {@code hashcode()} built with {@link EqualsBuilder} and
+ * {@link HashCodeBuilder}.</p>
+ *
+ * <p>Two Objects that compare equal using {@code equals(Object)} should normally
+ * also compare equal using {@code compareTo(Object)}.</p>
+ *
+ * <p>All relevant fields should be included in the calculation of the
+ * comparison. Derived fields may be ignored. The same fields, in the same
+ * order, should be used in both {@code compareTo(Object)} and
+ * {@code equals(Object)}.</p>
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class MyClass {
+ * String field1;
+ * int field2;
+ * boolean field3;
+ *
+ * ...
+ *
+ * public int compareTo(Object o) {
+ * MyClass myClass = (MyClass) o;
+ * return new CompareToBuilder()
+ * .appendSuper(super.compareTo(o)
+ * .append(this.field1, myClass.field1)
+ * .append(this.field2, myClass.field2)
+ * .append(this.field3, myClass.field3)
+ * .toComparison();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Values are compared in the order they are appended to the builder. If any comparison returns
+ * a non-zero result, then that value will be the result returned by {@code toComparison()} and all
+ * subsequent comparisons are skipped.</p>
+ *
+ * <p>Alternatively, there are {@link #reflectionCompare(Object, Object) reflectionCompare} methods that use
+ * reflection to determine the fields to append. Because fields can be private,
+ * {@code reflectionCompare} uses {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} to
+ * bypass normal access control checks. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than appending explicitly.</p>
+ *
+ * <p>A typical implementation of {@code compareTo(Object)} using
+ * {@code reflectionCompare} looks like:</p>
+
+ * <pre>
+ * public int compareTo(Object o) {
+ * return CompareToBuilder.reflectionCompare(this, o);
+ * }
+ * </pre>
+ *
+ * <p>The reflective methods compare object fields in the order returned by
+ * {@link Class#getDeclaredFields()}. The fields of the class are compared first, followed by those
+ * of its parent classes (in order from the bottom to the top of the class hierarchy).</p>
+ *
+ * @see Comparable
+ * @see Object#equals(Object)
+ * @see Object#hashCode()
+ * @see EqualsBuilder
+ * @see HashCodeBuilder
+ * @since 1.0
+ */
+public class CompareToBuilder implements Builder<Integer> {
+
+ /**
+ * Current state of the comparison as appended fields are checked.
+ */
+ private int comparison;
+
+ /**
+ * Constructor for CompareToBuilder.
+ *
+ * <p>Starts off assuming that the objects are equal. Multiple calls are
+ * then made to the various append methods, followed by a call to
+ * {@link #toComparison} to get the result.</p>
+ */
+ public CompareToBuilder() {
+ comparison = 0;
+ }
+
+ /**
+ * Compares two {@link Object}s via reflection.
+ *
+ * <p>Fields can be private, thus {@code AccessibleObject.setAccessible}
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>Transient members will be not be compared, as they are likely derived
+ * fields</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both {@code lhs} and {@code rhs} are {@code null},
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return a negative integer, zero, or a positive integer as {@code lhs}
+ * is less than, equal to, or greater than {@code rhs}
+ * @throws NullPointerException if either (but not both) parameters are
+ * {@code null}
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ */
+ public static int reflectionCompare(final Object lhs, final Object rhs) {
+ return reflectionCompare(lhs, rhs, false, null);
+ }
+
+ /**
+ * Compares two {@link Object}s via reflection.
+ *
+ * <p>Fields can be private, thus {@code AccessibleObject.setAccessible}
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If {@code compareTransients} is {@code true},
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both {@code lhs} and {@code rhs} are {@code null},
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @return a negative integer, zero, or a positive integer as {@code lhs}
+ * is less than, equal to, or greater than {@code rhs}
+ * @throws NullPointerException if either {@code lhs} or {@code rhs}
+ * (but not both) is {@code null}
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ */
+ public static int reflectionCompare(final Object lhs, final Object rhs, final boolean compareTransients) {
+ return reflectionCompare(lhs, rhs, compareTransients, null);
+ }
+
+ /**
+ * Compares two {@link Object}s via reflection.
+ *
+ * <p>Fields can be private, thus {@code AccessibleObject.setAccessible}
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If {@code compareTransients} is {@code true},
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both {@code lhs} and {@code rhs} are {@code null},
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields Collection of String fields to exclude
+ * @return a negative integer, zero, or a positive integer as {@code lhs}
+ * is less than, equal to, or greater than {@code rhs}
+ * @throws NullPointerException if either {@code lhs} or {@code rhs}
+ * (but not both) is {@code null}
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ * @since 2.2
+ */
+ public static int reflectionCompare(final Object lhs, final Object rhs, final Collection<String> excludeFields) {
+ return reflectionCompare(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * Compares two {@link Object}s via reflection.
+ *
+ * <p>Fields can be private, thus {@code AccessibleObject.setAccessible}
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If {@code compareTransients} is {@code true},
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Superclass fields will be compared</li>
+ * </ul>
+ *
+ * <p>If both {@code lhs} and {@code rhs} are {@code null},
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param excludeFields array of fields to exclude
+ * @return a negative integer, zero, or a positive integer as {@code lhs}
+ * is less than, equal to, or greater than {@code rhs}
+ * @throws NullPointerException if either {@code lhs} or {@code rhs}
+ * (but not both) is {@code null}
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ * @since 2.2
+ */
+ public static int reflectionCompare(final Object lhs, final Object rhs, final String... excludeFields) {
+ return reflectionCompare(lhs, rhs, false, null, excludeFields);
+ }
+
+ /**
+ * Compares two {@link Object}s via reflection.
+ *
+ * <p>Fields can be private, thus {@code AccessibleObject.setAccessible}
+ * is used to bypass normal access control checks. This will fail under a
+ * security manager unless the appropriate permissions are set.</p>
+ *
+ * <ul>
+ * <li>Static fields will not be compared</li>
+ * <li>If the {@code compareTransients} is {@code true},
+ * compares transient members. Otherwise ignores them, as they
+ * are likely derived fields.</li>
+ * <li>Compares superclass fields up to and including {@code reflectUpToClass}.
+ * If {@code reflectUpToClass} is {@code null}, compares all superclass fields.</li>
+ * </ul>
+ *
+ * <p>If both {@code lhs} and {@code rhs} are {@code null},
+ * they are considered equal.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param compareTransients whether to compare transient fields
+ * @param reflectUpToClass last superclass for which fields are compared
+ * @param excludeFields fields to exclude
+ * @return a negative integer, zero, or a positive integer as {@code lhs}
+ * is less than, equal to, or greater than {@code rhs}
+ * @throws NullPointerException if either {@code lhs} or {@code rhs}
+ * (but not both) is {@code null}
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ * @since 2.2 (2.0 as {@code reflectionCompare(Object, Object, boolean, Class)})
+ */
+ public static int reflectionCompare(
+ final Object lhs,
+ final Object rhs,
+ final boolean compareTransients,
+ final Class<?> reflectUpToClass,
+ final String... excludeFields) {
+
+ if (lhs == rhs) {
+ return 0;
+ }
+ Objects.requireNonNull(lhs, "lhs");
+ Objects.requireNonNull(rhs, "rhs");
+
+ Class<?> lhsClazz = lhs.getClass();
+ if (!lhsClazz.isInstance(rhs)) {
+ throw new ClassCastException();
+ }
+ final CompareToBuilder compareToBuilder = new CompareToBuilder();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
+ lhsClazz = lhsClazz.getSuperclass();
+ reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
+ }
+ return compareToBuilder.toComparison();
+ }
+
+ /**
+ * Appends to {@code builder} the comparison of {@code lhs}
+ * to {@code rhs} using the fields defined in {@code clazz}.
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param clazz {@link Class} that defines fields to be compared
+ * @param builder {@link CompareToBuilder} to append to
+ * @param useTransients whether to compare transient fields
+ * @param excludeFields fields to exclude
+ */
+ private static void reflectionAppend(
+ final Object lhs,
+ final Object rhs,
+ final Class<?> clazz,
+ final CompareToBuilder builder,
+ final boolean useTransients,
+ final String[] excludeFields) {
+
+ final Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
+ final Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && !f.getName().contains("$")
+ && (useTransients || !Modifier.isTransient(f.getModifiers()))
+ && !Modifier.isStatic(f.getModifiers())) {
+ try {
+ builder.append(f.get(lhs), f.get(rhs));
+ } 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 InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ }
+
+ /**
+ * Appends to the {@code builder} the {@code compareTo(Object)}
+ * result of the superclass.
+ *
+ * @param superCompareTo result of calling {@code super.compareTo(Object)}
+ * @return this - used to chain append calls
+ * @since 2.0
+ */
+ public CompareToBuilder appendSuper(final int superCompareTo) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = superCompareTo;
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@link Object}s.
+ *
+ * <ol>
+ * <li>Check if {@code lhs == rhs}</li>
+ * <li>Check if either {@code lhs} or {@code rhs} is {@code null},
+ * a {@code null} object is less than a non-{@code null} object</li>
+ * <li>Check the object contents</li>
+ * </ol>
+ *
+ * <p>{@code lhs} must either be an array or implement {@link Comparable}.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @return this - used to chain append calls
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ */
+ public CompareToBuilder append(final Object lhs, final Object rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@link Object}s.
+ *
+ * <ol>
+ * <li>Check if {@code lhs == rhs}</li>
+ * <li>Check if either {@code lhs} or {@code rhs} is {@code null},
+ * a {@code null} object is less than a non-{@code null} object</li>
+ * <li>Check the object contents</li>
+ * </ol>
+ *
+ * <p>If {@code lhs} is an array, array comparison methods will be used.
+ * Otherwise {@code comparator} will be used to compare the objects.
+ * If {@code comparator} is {@code null}, {@code lhs} must
+ * implement {@link Comparable} instead.</p>
+ *
+ * @param lhs left-hand object
+ * @param rhs right-hand object
+ * @param comparator {@link Comparator} used to compare the objects,
+ * {@code null} means treat lhs as {@link Comparable}
+ * @return this - used to chain append calls
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ * @since 2.0
+ */
+ public CompareToBuilder append(final Object lhs, final Object rhs, final Comparator<?> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (ObjectUtils.isArray(lhs)) {
+ // factor out array case in order to keep method small enough to be inlined
+ appendArray(lhs, rhs, comparator);
+ } else // the simple case, not an array, just test the element
+ if (comparator == null) {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparable<Object> comparable = (Comparable<Object>) lhs;
+ comparison = comparable.compareTo(rhs);
+ } else {
+ @SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
+ final Comparator<Object> comparator2 = (Comparator<Object>) comparator;
+ comparison = comparator2.compare(lhs, rhs);
+ }
+ return this;
+ }
+
+ private void appendArray(final Object lhs, final Object rhs, final Comparator<?> comparator) {
+ // switch on type of array, to dispatch to the correct handler
+ // handles multidimensional arrays
+ // throws a ClassCastException if rhs is not the correct array type
+ if (lhs instanceof long[]) {
+ append((long[]) lhs, (long[]) rhs);
+ } else if (lhs instanceof int[]) {
+ append((int[]) lhs, (int[]) rhs);
+ } else if (lhs instanceof short[]) {
+ append((short[]) lhs, (short[]) rhs);
+ } else if (lhs instanceof char[]) {
+ append((char[]) lhs, (char[]) rhs);
+ } else if (lhs instanceof byte[]) {
+ append((byte[]) lhs, (byte[]) rhs);
+ } else if (lhs instanceof double[]) {
+ append((double[]) lhs, (double[]) rhs);
+ } else if (lhs instanceof float[]) {
+ append((float[]) lhs, (float[]) rhs);
+ } else if (lhs instanceof boolean[]) {
+ append((boolean[]) lhs, (boolean[]) rhs);
+ } else {
+ // not an array of primitives
+ // throws a ClassCastException if rhs is not an array
+ append((Object[]) lhs, (Object[]) rhs, comparator);
+ }
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code long}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final long lhs, final long rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Long.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code int}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final int lhs, final int rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Integer.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code short}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final short lhs, final short rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Short.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code char}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final char lhs, final char rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Character.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code byte}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final byte lhs, final byte rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Byte.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code double}s.
+ *
+ * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * {@link HashCodeBuilder}.</p>
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final double lhs, final double rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Double.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code float}s.
+ *
+ * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * {@link HashCodeBuilder}.</p>
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final float lhs, final float rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ comparison = Float.compare(lhs, rhs);
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the comparison of
+ * two {@code booleans}s.
+ *
+ * @param lhs left-hand value
+ * @param rhs right-hand value
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final boolean lhs, final boolean rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs) {
+ comparison = 1;
+ } else {
+ comparison = -1;
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@link Object} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a short length array is less than a long length array</li>
+ * <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li>
+ * </ol>
+ *
+ * <p>This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.</p>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ */
+ public CompareToBuilder append(final Object[] lhs, final Object[] rhs) {
+ return append(lhs, rhs, null);
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@link Object} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a short length array is less than a long length array</li>
+ * <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li>
+ * </ol>
+ *
+ * <p>This method will also will be called for the top level of multi-dimensional,
+ * ragged, and multi-typed arrays.</p>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @param comparator {@link Comparator} to use to compare the array elements,
+ * {@code null} means to treat {@code lhs} elements as {@link Comparable}.
+ * @return this - used to chain append calls
+ * @throws ClassCastException if {@code rhs} is not assignment-compatible
+ * with {@code lhs}
+ * @since 2.0
+ */
+ public CompareToBuilder append(final Object[] lhs, final Object[] rhs, final Comparator<?> comparator) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i], comparator);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code long} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(long, long)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final long[] lhs, final long[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code int} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(int, int)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final int[] lhs, final int[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code short} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(short, short)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final short[] lhs, final short[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code char} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(char, char)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final char[] lhs, final char[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code byte} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(byte, byte)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final byte[] lhs, final byte[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code double} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(double, double)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final double[] lhs, final double[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code float} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(float, float)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final float[] lhs, final float[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Appends to the {@code builder} the deep comparison of
+ * two {@code boolean} arrays.
+ *
+ * <ol>
+ * <li>Check if arrays are the same using {@code ==}</li>
+ * <li>Check if for {@code null}, {@code null} is less than non-{@code null}</li>
+ * <li>Check array length, a shorter length array is less than a longer length array</li>
+ * <li>Check array contents element by element using {@link #append(boolean, boolean)}</li>
+ * </ol>
+ *
+ * @param lhs left-hand array
+ * @param rhs right-hand array
+ * @return this - used to chain append calls
+ */
+ public CompareToBuilder append(final boolean[] lhs, final boolean[] rhs) {
+ if (comparison != 0) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null) {
+ comparison = -1;
+ return this;
+ }
+ if (rhs == null) {
+ comparison = 1;
+ return this;
+ }
+ if (lhs.length != rhs.length) {
+ comparison = lhs.length < rhs.length ? -1 : 1;
+ return this;
+ }
+ for (int i = 0; i < lhs.length && comparison == 0; i++) {
+ append(lhs[i], rhs[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a negative integer, a positive integer, or zero as
+ * the {@code builder} has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result
+ * @see #build()
+ */
+ public int toComparison() {
+ return comparison;
+ }
+
+ /**
+ * Returns a negative Integer, a positive Integer, or zero as
+ * the {@code builder} has judged the "left-hand" side
+ * as less than, greater than, or equal to the "right-hand"
+ * side.
+ *
+ * @return final comparison result as an Integer
+ * @see #toComparison()
+ * @since 3.0
+ */
+ @Override
+ public Integer build() {
+ return Integer.valueOf(toComparison());
+ }
+}
+
diff --git a/src/main/java/org/apache/commons/lang3/builder/Diff.java b/src/main/java/org/apache/commons/lang3/builder/Diff.java
new file mode 100644
index 000000000..763cd9ae0
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/Diff.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.Type;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * A {@link Diff} contains the differences between two {@link Diffable} class
+ * fields.
+ *
+ * <p>
+ * Typically, {@link Diff}s are retrieved by using a {@link DiffBuilder} to
+ * produce a {@link DiffResult}, containing the differences between two objects.
+ * </p>
+ *
+ * @param <T>
+ * The type of object contained within this {@link Diff}. Differences
+ * between primitive objects are stored as their Object wrapper
+ * equivalent.
+ * @since 3.3
+ */
+public abstract class Diff<T> extends Pair<T, T> {
+
+ private static final long serialVersionUID = 1L;
+
+ /** The field type. */
+ private final Type type;
+
+ /** The field name. */
+ private final String fieldName;
+
+ /**
+ * Constructs a new {@link Diff} for the given field name.
+ *
+ * @param fieldName
+ * the field name
+ */
+ protected Diff(final String fieldName) {
+ this.type = ObjectUtils.defaultIfNull(
+ TypeUtils.getTypeArguments(getClass(), Diff.class).get(
+ Diff.class.getTypeParameters()[0]), Object.class);
+ this.fieldName = fieldName;
+ }
+
+ /**
+ * Gets the type of the field.
+ *
+ * @return the field type
+ */
+ public final Type getType() {
+ return type;
+ }
+
+ /**
+ * Gets the name of the field.
+ *
+ * @return the field name
+ */
+ public final String getFieldName() {
+ return fieldName;
+ }
+
+ /**
+ * Returns a {@link String} representation of the {@link Diff}, with the
+ * following format:
+ *
+ * <pre>
+ * [fieldname: left-value, right-value]
+ * </pre>
+ *
+ * @return the string representation
+ */
+ @Override
+ 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
new file mode 100644
index 000000000..9b34038e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java
@@ -0,0 +1,932 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Assists in implementing {@link Diffable#diff(Object)} methods.
+ *
+ * <p>
+ * To use this class, write code as follows:
+ * </p>
+ *
+ * <pre>
+ * public class Person implements Diffable&lt;Person&gt; {
+ * String name;
+ * int age;
+ * boolean smoker;
+ *
+ * ...
+ *
+ * public DiffResult diff(Person obj) {
+ * // No need for null check, as NullPointerException correct if obj is null
+ * return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ * .append("name", this.name, obj.name)
+ * .append("age", this.age, obj.age)
+ * .append("smoker", this.smoker, obj.smoker)
+ * .build();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * The {@link ToStringStyle} passed to the constructor is embedded in the
+ * returned {@link DiffResult} and influences the style of the
+ * {@code DiffResult.toString()} method. This style choice can be overridden by
+ * calling {@link DiffResult#toString(ToStringStyle)}.
+ * </p>
+ *
+ * @param <T> type of the left and right object.
+ * @see Diffable
+ * @see Diff
+ * @see DiffResult
+ * @see ToStringStyle
+ * @since 3.3
+ */
+public class DiffBuilder<T> implements Builder<DiffResult<T>> {
+
+ private final List<Diff<?>> diffs;
+ private final boolean objectsTriviallyEqual;
+ private final T left;
+ private final T right;
+ private final ToStringStyle style;
+
+ /**
+ * Constructs a builder for the specified objects with the specified style.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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
+ * @param testTriviallyEqual
+ * If true, this will test if lhs and rhs are the same or equal.
+ * All of the append(fieldName, lhs, rhs) methods will abort
+ * without creating a field {@link Diff} if the trivially equal
+ * test is enabled and returns true. The result of this test
+ * is never changed throughout the life of this {@link DiffBuilder}.
+ * @throws IllegalArgumentException
+ * if {@code lhs} or {@code rhs} is {@code null}
+ * @since 3.4
+ */
+ public DiffBuilder(final T lhs, final T rhs,
+ final ToStringStyle style, final boolean testTriviallyEqual) {
+
+ Objects.requireNonNull(lhs, "lhs");
+ Objects.requireNonNull(rhs, "rhs");
+
+ this.diffs = new ArrayList<>();
+ this.left = lhs;
+ this.right = rhs;
+ this.style = style;
+
+ // Don't compare any fields if objects equal
+ this.objectsTriviallyEqual = testTriviallyEqual && (lhs == rhs || lhs.equals(rhs));
+ }
+
+ /**
+ * Constructs a builder for the specified objects with the specified style.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)}
+ * with the testTriviallyEqual flag enabled.
+ * </p>
+ *
+ * @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 IllegalArgumentException
+ * 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.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code boolean}
+ * @param rhs
+ * the right-hand {@code boolean}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final boolean lhs,
+ final boolean rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Boolean>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Boolean getLeft() {
+ return Boolean.valueOf(lhs);
+ }
+
+ @Override
+ public Boolean getRight() {
+ return Boolean.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code boolean[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code boolean[]}
+ * @param rhs
+ * the right-hand {@code boolean[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final boolean[] lhs,
+ final boolean[] rhs) {
+ validateFieldNameNotNull(fieldName);
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Boolean[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Boolean[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Boolean[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code byte}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code byte}
+ * @param rhs
+ * the right-hand {@code byte}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final byte lhs,
+ final byte rhs) {
+ validateFieldNameNotNull(fieldName);
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Byte>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Byte getLeft() {
+ return Byte.valueOf(lhs);
+ }
+
+ @Override
+ public Byte getRight() {
+ return Byte.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code byte[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code byte[]}
+ * @param rhs
+ * the right-hand {@code byte[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final byte[] lhs,
+ final byte[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Byte[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Byte[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Byte[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code char}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code char}
+ * @param rhs
+ * the right-hand {@code char}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final char lhs,
+ final char rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Character>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Character getLeft() {
+ return Character.valueOf(lhs);
+ }
+
+ @Override
+ public Character getRight() {
+ return Character.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code char[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code char[]}
+ * @param rhs
+ * the right-hand {@code char[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final char[] lhs,
+ final char[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Character[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Character[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Character[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code double}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code double}
+ * @param rhs
+ * the right-hand {@code double}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final double lhs,
+ final double rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (Double.doubleToLongBits(lhs) != Double.doubleToLongBits(rhs)) {
+ diffs.add(new Diff<Double>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Double getLeft() {
+ return Double.valueOf(lhs);
+ }
+
+ @Override
+ public Double getRight() {
+ return Double.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code double[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code double[]}
+ * @param rhs
+ * the right-hand {@code double[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final double[] lhs,
+ final double[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Double[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Double[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Double[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code float}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code float}
+ * @param rhs
+ * the right-hand {@code float}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final float lhs,
+ final float rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (Float.floatToIntBits(lhs) != Float.floatToIntBits(rhs)) {
+ diffs.add(new Diff<Float>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Float getLeft() {
+ return Float.valueOf(lhs);
+ }
+
+ @Override
+ public Float getRight() {
+ return Float.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code float[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code float[]}
+ * @param rhs
+ * the right-hand {@code float[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final float[] lhs,
+ final float[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Float[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Float[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Float[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code int}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code int}
+ * @param rhs
+ * the right-hand {@code int}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final int lhs,
+ final int rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Integer>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Integer getLeft() {
+ return Integer.valueOf(lhs);
+ }
+
+ @Override
+ public Integer getRight() {
+ return Integer.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code int[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code int[]}
+ * @param rhs
+ * the right-hand {@code int[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final int[] lhs,
+ final int[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Integer[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Integer[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Integer[] getRight() {
+ return ArrayUtils.toObject(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code long}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code long}
+ * @param rhs
+ * the right-hand {@code long}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final long lhs,
+ final long rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Long>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Long getLeft() {
+ return Long.valueOf(lhs);
+ }
+
+ @Override
+ public Long getRight() {
+ return Long.valueOf(rhs);
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Test if two {@code long[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code long[]}
+ * @param rhs
+ * the right-hand {@code long[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final long[] lhs,
+ final long[] rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Long[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Long[] getLeft() {
+ return ArrayUtils.toObject(lhs);
+ }
+
+ @Override
+ public Long[] getRight() {
+ return ArrayUtils.toObject(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 IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final short lhs,
+ final short rhs) {
+ validateFieldNameNotNull(fieldName);
+
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs != rhs) {
+ diffs.add(new Diff<Short>(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 IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> 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<Short[]>(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.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@link Object}
+ * @param rhs
+ * the right-hand {@link Object}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final Object lhs,
+ final Object rhs) {
+ validateFieldNameNotNull(fieldName);
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+
+ final Object objectToTest;
+ if (lhs != null) {
+ objectToTest = lhs;
+ } else {
+ // rhs cannot be null, as lhs != rhs
+ objectToTest = rhs;
+ }
+
+ if (ObjectUtils.isArray(objectToTest)) {
+ if (objectToTest instanceof boolean[]) {
+ return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
+ }
+ if (objectToTest instanceof byte[]) {
+ return append(fieldName, (byte[]) lhs, (byte[]) rhs);
+ }
+ if (objectToTest instanceof char[]) {
+ return append(fieldName, (char[]) lhs, (char[]) rhs);
+ }
+ if (objectToTest instanceof double[]) {
+ return append(fieldName, (double[]) lhs, (double[]) rhs);
+ }
+ if (objectToTest instanceof float[]) {
+ return append(fieldName, (float[]) lhs, (float[]) rhs);
+ }
+ if (objectToTest instanceof int[]) {
+ return append(fieldName, (int[]) lhs, (int[]) rhs);
+ }
+ if (objectToTest instanceof long[]) {
+ return append(fieldName, (long[]) lhs, (long[]) rhs);
+ }
+ if (objectToTest instanceof short[]) {
+ return append(fieldName, (short[]) lhs, (short[]) rhs);
+ }
+
+ return append(fieldName, (Object[]) lhs, (Object[]) rhs);
+ }
+
+ // Not array type
+ if (lhs != null && lhs.equals(rhs)) {
+ return this;
+ }
+
+ diffs.add(new Diff<Object>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Object getLeft() {
+ return lhs;
+ }
+
+ @Override
+ public Object getRight() {
+ return rhs;
+ }
+ });
+
+ return this;
+ }
+
+ /**
+ * Test if two {@code Object[]}s are equal.
+ *
+ * @param fieldName
+ * the field name
+ * @param lhs
+ * the left-hand {@code Object[]}
+ * @param rhs
+ * the right-hand {@code Object[]}
+ * @return this
+ * @throws IllegalArgumentException
+ * if field name is {@code null}
+ */
+ public DiffBuilder<T> append(final String fieldName, final Object[] lhs,
+ final Object[] rhs) {
+ validateFieldNameNotNull(fieldName);
+ if (objectsTriviallyEqual) {
+ return this;
+ }
+
+ if (!Arrays.equals(lhs, rhs)) {
+ diffs.add(new Diff<Object[]>(fieldName) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Object[] getLeft() {
+ return lhs;
+ }
+
+ @Override
+ public Object[] getRight() {
+ return rhs;
+ }
+ });
+ }
+
+ return this;
+ }
+
+ /**
+ * Append diffs from another {@link DiffResult}.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <pre>
+ * public class Person implements Diffable&lt;Person&gt; {
+ * String name;
+ * Address address; // implements Diffable&lt;Address&gt;
+ *
+ * ...
+ *
+ * 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();
+ * }
+ * }
+ * </pre>
+ *
+ * @param fieldName
+ * the field name
+ * @param diffResult
+ * the {@link DiffResult} to append
+ * @return this
+ * @throws NullPointerException if field name is {@code null}
+ * @since 3.5
+ */
+ public DiffBuilder<T> append(final String fieldName, final DiffResult<T> 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;
+ }
+
+ /**
+ * Builds a {@link DiffResult} based on the differences appended to this
+ * builder.
+ *
+ * @return a {@link DiffResult} containing the differences between the two
+ * objects.
+ */
+ @Override
+ public DiffResult<T> build() {
+ return new DiffResult<>(left, right, diffs, style);
+ }
+
+ private void validateFieldNameNotNull(final String fieldName) {
+ Objects.requireNonNull(fieldName, "fieldName");
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffResult.java b/src/main/java/org/apache/commons/lang3/builder/DiffResult.java
new file mode 100644
index 000000000..a5c2d1053
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/DiffResult.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link DiffResult} contains a collection of the differences between two
+ * {@link Diffable} objects. Typically these differences are displayed using
+ * {@link #toString()} method, which returns a string describing the fields that
+ * differ between the objects.
+ *
+ * <p>
+ * Use a {@link DiffBuilder} to build a {@link DiffResult} comparing two objects.
+ * </p>
+ * @param <T> type of the left and right object.
+ *
+ * @since 3.3
+ */
+public class DiffResult<T> implements Iterable<Diff<?>> {
+
+ /**
+ * The {@link String} returned when the objects have no differences:
+ * {@value}
+ *
+ */
+ public static final String OBJECTS_SAME_STRING = "";
+
+ private static final String DIFFERS_STRING = "differs from";
+
+ private final List<Diff<?>> diffList;
+ private final T lhs;
+ private final T rhs;
+ private final ToStringStyle style;
+
+ /**
+ * Creates a {@link DiffResult} containing the differences between two
+ * objects.
+ *
+ * @param lhs
+ * the left-hand object
+ * @param rhs
+ * the right-hand object
+ * @param diffList
+ * the list of differences, may be empty
+ * @param style
+ * the style to use for the {@link #toString()} method. May be
+ * {@code null}, in which case
+ * {@link ToStringStyle#DEFAULT_STYLE} is used
+ * @throws NullPointerException if {@code lhs}, {@code rhs} or {@code diffs} is {@code null}
+ */
+ DiffResult(final T lhs, final T rhs, final List<Diff<?>> diffList,
+ final ToStringStyle style) {
+ Objects.requireNonNull(lhs, "lhs");
+ Objects.requireNonNull(rhs, "rhs");
+ Objects.requireNonNull(diffList, "diffList");
+
+ this.diffList = diffList;
+ this.lhs = lhs;
+ this.rhs = rhs;
+
+ if (style == null) {
+ this.style = ToStringStyle.DEFAULT_STYLE;
+ } else {
+ this.style = style;
+ }
+ }
+
+ /**
+ * Returns the object the right object has been compared to.
+ *
+ * @return the left object of the diff
+ * @since 3.10
+ */
+ public T getLeft() {
+ return this.lhs;
+ }
+
+ /**
+ * Returns the object the left object has been compared to.
+ *
+ * @return the right object of the diff
+ * @since 3.10
+ */
+ public T getRight() {
+ 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<Diff<?>> 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.
+ *
+ * @return the style
+ */
+ public ToStringStyle getToStringStyle() {
+ return style;
+ }
+
+ /**
+ * Builds a {@link String} description of the differences contained within
+ * this {@link DiffResult}. A {@link ToStringBuilder} is used for each object
+ * and the style of the output is governed by the {@link ToStringStyle}
+ * passed to the constructor.
+ *
+ * <p>
+ * If there are no differences stored in this list, the method will return
+ * {@link #OBJECTS_SAME_STRING}. Otherwise, using the example given in
+ * {@link Diffable} and {@link ToStringStyle#SHORT_PREFIX_STYLE}, an output
+ * might be:
+ * </p>
+ *
+ * <pre>
+ * Person[name=John Doe,age=32] differs from Person[name=Joe Bloggs,age=26]
+ * </pre>
+ *
+ * <p>
+ * This indicates that the objects differ in name and age, but not in
+ * smoking status.
+ * </p>
+ *
+ * <p>
+ * To use a different {@link ToStringStyle} for an instance of this class,
+ * use {@link #toString(ToStringStyle)}.
+ * </p>
+ *
+ * @return a {@link String} description of the differences.
+ */
+ @Override
+ public String toString() {
+ return toString(style);
+ }
+
+ /**
+ * Builds a {@link String} description of the differences contained within
+ * this {@link DiffResult}, using the supplied {@link ToStringStyle}.
+ *
+ * @param style
+ * the {@link ToStringStyle} to use when outputting the objects
+ *
+ * @return a {@link String} description of the differences.
+ */
+ public String toString(final ToStringStyle style) {
+ if (diffList.isEmpty()) {
+ return OBJECTS_SAME_STRING;
+ }
+
+ final ToStringBuilder lhsBuilder = new ToStringBuilder(lhs, style);
+ final ToStringBuilder rhsBuilder = new ToStringBuilder(rhs, style);
+
+ diffList.forEach(diff -> {
+ lhsBuilder.append(diff.getFieldName(), diff.getLeft());
+ rhsBuilder.append(diff.getFieldName(), diff.getRight());
+ });
+
+ 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<Diff<?>> iterator() {
+ return diffList.iterator();
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/Diffable.java b/src/main/java/org/apache/commons/lang3/builder/Diffable.java
new file mode 100644
index 000000000..cf4b33f0e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/Diffable.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+/**
+ * {@link Diffable} classes can be compared with other objects
+ * for differences. The {@link DiffResult} object retrieved can be queried
+ * for a list of differences or printed using the {@link DiffResult#toString()}.
+ *
+ * <p>The calculation of the differences is <i>consistent with equals</i> if
+ * and only if {@code d1.equals(d2)} implies {@code d1.diff(d2) == ""}.
+ * It is strongly recommended that implementations are consistent with equals
+ * to avoid confusion. Note that {@code null} is not an instance of any class
+ * and {@code d1.diff(null)} should throw a {@link NullPointerException}.</p>
+ *
+ * <p>
+ * {@link Diffable} classes lend themselves well to unit testing, in which a
+ * easily readable description of the differences between an anticipated result and
+ * an actual result can be retrieved. For example:
+ * </p>
+ * <pre>
+ * Assert.assertEquals(expected.diff(result), expected, result);
+ * </pre>
+ *
+ * @param <T> the type of objects that this object may be differentiated against
+ * @since 3.3
+ */
+@FunctionalInterface
+public interface Diffable<T> {
+
+ /**
+ * Retrieves a list of the differences between
+ * this object and the supplied object.
+ *
+ * @param obj the object to diff against, can be {@code null}
+ * @return a list of differences
+ * @throws NullPointerException if the specified object is {@code null}
+ */
+ DiffResult<T> diff(T obj);
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java
new file mode 100644
index 000000000..ae321c9fe
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java
@@ -0,0 +1,1124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Assists in implementing {@link Object#equals(Object)} methods.
+ *
+ * <p> This class provides methods to build a good equals method for any
+ * class. It follows rules laid out in
+ * <a href="https://www.oracle.com/java/technologies/effectivejava.html">Effective Java</a>
+ * , by Joshua Bloch. In particular the rule for comparing {@code doubles},
+ * {@code floats}, and arrays can be tricky. Also, making sure that
+ * {@code equals()} and {@code hashCode()} are consistent can be
+ * difficult.</p>
+ *
+ * <p>Two Objects that compare as equals must generate the same hash code,
+ * but two Objects with the same hash code do not have to be equal.</p>
+ *
+ * <p>All relevant fields should be included in the calculation of equals.
+ * Derived fields may be ignored. In particular, any field used in
+ * generating a hash code must be used in the equals method, and vice
+ * versa.</p>
+ *
+ * <p>Typical use for the code is as follows:</p>
+ * <pre>
+ * public boolean equals(Object obj) {
+ * if (obj == null) { return false; }
+ * if (obj == this) { return true; }
+ * if (obj.getClass() != getClass()) {
+ * return false;
+ * }
+ * MyClass rhs = (MyClass) obj;
+ * return new EqualsBuilder()
+ * .appendSuper(super.equals(obj))
+ * .append(field1, rhs.field1)
+ * .append(field2, rhs.field2)
+ * .append(field3, rhs.field3)
+ * .isEquals();
+ * }
+ * </pre>
+ *
+ * <p> Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * {@code reflectionEquals}, uses {@code AccessibleObject.setAccessible} to
+ * change the visibility of the fields. This will fail under a security
+ * manager, unless the appropriate permissions are set up correctly. It is
+ * also slower than testing explicitly. Non-primitive fields are compared using
+ * {@code equals()}.</p>
+ *
+ * <p> A typical invocation for this method would look like:</p>
+ * <pre>
+ * public boolean equals(Object obj) {
+ * return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ * </pre>
+ *
+ * <p>The {@link EqualsExclude} annotation can be used to exclude fields from being
+ * used by the {@code reflectionEquals} methods.</p>
+ *
+ * @since 1.0
+ */
+public class EqualsBuilder implements Builder<Boolean> {
+
+ /**
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ *
+ * @since 3.0
+ */
+ private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hash code really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.identityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ * Returns the registry of object pairs being traversed by the reflection
+ * methods in the current thread.
+ *
+ * @return Set the registry of objects being traversed
+ * @since 3.0
+ */
+ static Set<Pair<IDKey, IDKey>> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * Converters value pair into a register pair.
+ *
+ * @param lhs {@code this} object
+ * @param rhs the other object
+ *
+ * @return the pair
+ */
+ static Pair<IDKey, IDKey> getRegisterPair(final Object lhs, final Object rhs) {
+ final IDKey left = new IDKey(lhs);
+ final IDKey right = new IDKey(rhs);
+ return Pair.of(left, right);
+ }
+
+ /**
+ * Returns {@code true} if the registry contains the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ * Objects might be swapped therefore a check is needed if the object pair
+ * is registered in given or swapped order.
+ *
+ * @param lhs {@code this} object to lookup in registry
+ * @param rhs the other object to lookup on registry
+ * @return boolean {@code true} if the registry contains the given object.
+ * @since 3.0
+ */
+ static boolean isRegistered(final Object lhs, final Object rhs) {
+ final Set<Pair<IDKey, IDKey>> registry = getRegistry();
+ final Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
+ final Pair<IDKey, IDKey> swappedPair = Pair.of(pair.getRight(), pair.getLeft());
+
+ return registry != null
+ && (registry.contains(pair) || registry.contains(swappedPair));
+ }
+
+ /**
+ * Registers the given object pair.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param lhs {@code this} object to register
+ * @param rhs the other object to register
+ */
+ private static void register(final Object lhs, final Object rhs) {
+ Set<Pair<IDKey, IDKey>> registry = getRegistry();
+ if (registry == null) {
+ registry = new HashSet<>();
+ REGISTRY.set(registry);
+ }
+ final Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
+ registry.add(pair);
+ }
+
+ /**
+ * Unregisters the given object pair.
+ *
+ * <p>
+ * 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<Pair<IDKey, IDKey>> 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<Class<?>> bypassReflectionClasses;
+ private Class<?> reflectUpToClass;
+ private String[] excludeFields;
+
+ /**
+ * Constructor for EqualsBuilder.
+ *
+ * <p>Starts off assuming that equals is {@code true}.</p>
+ * @see Object#equals(Object)
+ */
+ public EqualsBuilder() {
+ // set up default classes to bypass reflection for
+ bypassReflectionClasses = new ArrayList<>();
+ bypassReflectionClasses.add(String.class); //hashCode field being lazy but not transient
+ }
+
+ /**
+ * Set whether to include transient fields when reflectively comparing objects.
+ * @param testTransients whether to test transient fields
+ * @return EqualsBuilder - used to chain calls.
+ * @since 3.6
+ */
+ public EqualsBuilder setTestTransients(final boolean testTransients) {
+ this.testTransients = testTransients;
+ return this;
+ }
+
+ /**
+ * Set 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 EqualsBuilder - used to chain calls.
+ * @see #setBypassReflectionClasses(List)
+ * @since 3.6
+ */
+ public EqualsBuilder setTestRecursive(final boolean testRecursive) {
+ this.testRecursive = testRecursive;
+ return this;
+ }
+
+ /**
+ * Set {@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.
+ *
+ * <p>Here you should name classes having non-transient fields which are cache fields being set lazily.<br>
+ * 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.</p>
+ * @param bypassReflectionClasses classes to bypass reflection test
+ * @return EqualsBuilder - used to chain calls.
+ * @see #setTestRecursive(boolean)
+ * @since 3.8
+ */
+ public EqualsBuilder setBypassReflectionClasses(final List<Class<?>> bypassReflectionClasses) {
+ this.bypassReflectionClasses = bypassReflectionClasses;
+ return this;
+ }
+
+ /**
+ * Set the superclass to reflect up to at reflective tests.
+ * @param reflectUpToClass the super class to reflect up to
+ * @return EqualsBuilder - used to chain calls.
+ * @since 3.6
+ */
+ public EqualsBuilder setReflectUpToClass(final Class<?> reflectUpToClass) {
+ this.reflectUpToClass = reflectUpToClass;
+ return this;
+ }
+
+ /**
+ * Set field names to be excluded by reflection tests.
+ * @param excludeFields the fields to exclude
+ * @return EqualsBuilder - used to chain calls.
+ * @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.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs {@code this} 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<String> excludeFields) {
+ return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * This method uses reflection to determine if the two {@link Object}s
+ * are equal.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs {@code this} 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.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs {@code this} object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @return {@code true} if the two Objects have tested equals.
+ *
+ * @see EqualsExclude
+ */
+ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) {
+ return reflectionEquals(lhs, rhs, testTransients, null);
+ }
+
+ /**
+ * This method uses reflection to determine if the two {@link Object}s
+ * are equal.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object.</p>
+ *
+ * @param lhs {@code this} 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.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object.</p>
+ *
+ * <p>If the testRecursive parameter is set to {@code true}, non primitive
+ * (and non primitive wrapper) field types will be compared by
+ * {@link EqualsBuilder} recursively instead of invoking their
+ * {@code equals()} method. Leading to a deep reflection equals test.
+ *
+ * @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 testRecursive whether to call reflection equals on non-primitive
+ * fields recursively.
+ * @param excludeFields array of field names to exclude from testing
+ * @return {@code true} if the two Objects have tested equals.
+ *
+ * @see EqualsExclude
+ * @since 3.6
+ */
+ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class<?> reflectUpToClass,
+ final boolean testRecursive, final String... excludeFields) {
+ if (lhs == rhs) {
+ return true;
+ }
+ if (lhs == null || rhs == null) {
+ return false;
+ }
+ return new EqualsBuilder()
+ .setExcludeFields(excludeFields)
+ .setReflectUpToClass(reflectUpToClass)
+ .setTestTransients(testTransients)
+ .setTestRecursive(testRecursive)
+ .reflectionAppend(lhs, rhs)
+ .isEquals();
+ }
+
+ /**
+ * Tests if two {@code objects} by using reflection.
+ *
+ * <p>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()}.</p>
+ *
+ * <p>If the testTransients field 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}.</p>
+ *
+ * <p>Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass in field {@code reflectUpToClass}.
+ * A null superclass is treated as java.lang.Object.</p>
+ *
+ * <p>Field names listed in field {@code excludeFields} will be ignored.</p>
+ *
+ * <p>If either class of the compared objects is contained in
+ * {@code bypassReflectionClasses}, both objects are compared by calling
+ * the equals method of the left-hand object with the right-hand object as an argument.</p>
+ *
+ * @param lhs the left-hand object
+ * @param rhs the right-hand object
+ * @return EqualsBuilder - used to chain calls.
+ */
+ public EqualsBuilder reflectionAppend(final Object lhs, final Object rhs) {
+ if (!isEquals) {
+ return this;
+ }
+ if (lhs == rhs) {
+ return this;
+ }
+ if (lhs == null || rhs == null) {
+ isEquals = false;
+ return this;
+ }
+
+ // Find the leaf class since there may be transients in the leaf
+ // class or in classes between the leaf and root.
+ // If we are not testing transients or a subclass has no ivars,
+ // then a subclass can test equals to a superclass.
+ final Class<?> lhsClass = lhs.getClass();
+ final Class<?> rhsClass = rhs.getClass();
+ Class<?> testClass;
+ if (lhsClass.isInstance(rhs)) {
+ testClass = lhsClass;
+ if (!rhsClass.isInstance(lhs)) {
+ // rhsClass is a subclass of lhsClass
+ testClass = rhsClass;
+ }
+ } else if (rhsClass.isInstance(lhs)) {
+ testClass = rhsClass;
+ if (!lhsClass.isInstance(rhs)) {
+ // lhsClass is a subclass of rhsClass
+ testClass = lhsClass;
+ }
+ } else {
+ // The two classes are not related.
+ isEquals = false;
+ return this;
+ }
+
+ try {
+ if (testClass.isArray()) {
+ append(lhs, rhs);
+ } else //If either class is being excluded, call normal object equals method on lhsClass.
+ if (bypassReflectionClasses != null
+ && (bypassReflectionClasses.contains(lhsClass) || bypassReflectionClasses.contains(rhsClass))) {
+ isEquals = lhs.equals(rhs);
+ } else {
+ reflectionAppend(lhs, rhs, testClass);
+ while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
+ testClass = testClass.getSuperclass();
+ reflectionAppend(lhs, rhs, testClass);
+ }
+ }
+ } catch (final IllegalArgumentException e) {
+ // In this case, we tried to test a subclass vs. a superclass and
+ // the subclass has ivars or the ivars are transient and
+ // we are testing transients.
+ // If a subclass has ivars that we are trying to test them, we get an
+ // exception and we know that the objects are not equal.
+ isEquals = false;
+ }
+ return this;
+ }
+
+ /**
+ * Appends the fields and values defined by the given object of the
+ * given Class.
+ *
+ * @param lhs the left-hand object
+ * @param rhs the right-hand object
+ * @param clazz the class to append details of
+ */
+ private void reflectionAppend(
+ final Object lhs,
+ final Object rhs,
+ final Class<?> clazz) {
+
+ if (isRegistered(lhs, rhs)) {
+ return;
+ }
+
+ try {
+ register(lhs, rhs);
+ final Field[] fields = clazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int i = 0; i < fields.length && isEquals; i++) {
+ final Field f = fields[i];
+ if (!ArrayUtils.contains(excludeFields, f.getName())
+ && !f.getName().contains("$")
+ && (testTransients || !Modifier.isTransient(f.getModifiers()))
+ && !Modifier.isStatic(f.getModifiers())
+ && !f.isAnnotationPresent(EqualsExclude.class)) {
+ try {
+ append(f.get(lhs), f.get(rhs));
+ } 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 InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(lhs, rhs);
+ }
+ }
+
+ /**
+ * Adds the result of {@code super.equals()} to this builder.
+ *
+ * @param superEquals the result of calling {@code super.equals()}
+ * @return EqualsBuilder - used to chain calls.
+ * @since 2.0
+ */
+ public EqualsBuilder appendSuper(final boolean superEquals) {
+ if (!isEquals) {
+ return this;
+ }
+ isEquals = superEquals;
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * {@link HashCodeBuilder}.</p>
+ *
+ * @param lhs the left-hand {@code double}
+ * @param rhs the right-hand {@code double}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
+ *
+ * <p>It is compatible with the hash code generated by
+ * {@link HashCodeBuilder}.</p>
+ *
+ * @param lhs the left-hand {@code float}
+ * @param rhs the right-hand {@code float}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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 EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>This also will be called for the top level of
+ * multi-dimensional, ragged, and multi-typed arrays.</p>
+ *
+ * <p>Note that this method does not compare the type of the arrays; it only
+ * compares the contents.</p>
+ *
+ * @param lhs the left-hand {@code Object[]}
+ * @param rhs the right-hand {@code Object[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(long, long)} is used.</p>
+ *
+ * @param lhs the left-hand {@code long[]}
+ * @param rhs the right-hand {@code long[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(int, int)} is used.</p>
+ *
+ * @param lhs the left-hand {@code int[]}
+ * @param rhs the right-hand {@code int[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(short, short)} is used.</p>
+ *
+ * @param lhs the left-hand {@code short[]}
+ * @param rhs the right-hand {@code short[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(char, char)} is used.</p>
+ *
+ * @param lhs the left-hand {@code char[]}
+ * @param rhs the right-hand {@code char[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(byte, byte)} is used.</p>
+ *
+ * @param lhs the left-hand {@code byte[]}
+ * @param rhs the right-hand {@code byte[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(double, double)} is used.</p>
+ *
+ * @param lhs the left-hand {@code double[]}
+ * @param rhs the right-hand {@code double[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(float, float)} is used.</p>
+ *
+ * @param lhs the left-hand {@code float[]}
+ * @param rhs the right-hand {@code float[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * <p>The method {@link #append(boolean, boolean)} is used.</p>
+ *
+ * @param lhs the left-hand {@code boolean[]}
+ * @param rhs the right-hand {@code boolean[]}
+ * @return EqualsBuilder - used to chain calls.
+ */
+ 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.
+ *
+ * @param isEquals The value to set.
+ * @since 2.1
+ */
+ protected void setEquals(final boolean isEquals) {
+ this.isEquals = isEquals;
+ }
+
+ /**
+ * Reset the EqualsBuilder so you can use the same object again
+ * @since 2.5
+ */
+ public void reset() {
+ this.isEquals = true;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/EqualsExclude.java b/src/main/java/org/apache/commons/lang3/builder/EqualsExclude.java
new file mode 100755
index 000000000..b4ede8f1e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/EqualsExclude.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to exclude a field from being used by
+ * the various {@code reflectionEquals} methods defined on
+ * {@link EqualsBuilder}.
+ *
+ * @since 3.5
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface EqualsExclude {
+ // empty
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java b/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java
new file mode 100644
index 000000000..f564db95a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/HashCodeBuilder.java
@@ -0,0 +1,936 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.commons.lang3.ArraySorter;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Assists in implementing {@link Object#hashCode()} methods.
+ *
+ * <p>
+ * This class enables a good {@code hashCode} method to be built for any class. It follows the rules laid out in
+ * the book <a href="https://www.oracle.com/technetwork/java/effectivejava-136174.html">Effective Java</a> by Joshua Bloch. Writing a
+ * good {@code hashCode} method is actually quite difficult. This class aims to simplify the process.
+ * </p>
+ *
+ * <p>
+ * The following is the approach taken. When appending a data field, the current total is multiplied by the
+ * multiplier then a relevant value
+ * for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then
+ * appending the integer 45 will create a hash code of 674, namely 17 * 37 + 45.
+ * </p>
+ *
+ * <p>
+ * All relevant fields from the object should be included in the {@code hashCode} method. Derived fields may be
+ * excluded. In general, any field used in the {@code equals} method must be used in the {@code hashCode}
+ * method.
+ * </p>
+ *
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ *
+ * <pre>
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * ...
+ *
+ * public int hashCode() {
+ * // you pick a hard-coded, randomly chosen, non-zero, odd number
+ * // ideally different for each class
+ * return new HashCodeBuilder(17, 37).
+ * append(name).
+ * append(age).
+ * append(smoker).
+ * toHashCode();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * If required, the superclass {@code hashCode()} can be added using {@link #appendSuper}.
+ * </p>
+ *
+ * <p>
+ * Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are
+ * usually private, the method, {@code reflectionHashCode}, uses {@code AccessibleObject.setAccessible}
+ * to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions
+ * are set up correctly. It is also slower than testing explicitly.
+ * </p>
+ *
+ * <p>
+ * A typical invocation for this method would look like:
+ * </p>
+ *
+ * <pre>
+ * public int hashCode() {
+ * return HashCodeBuilder.reflectionHashCode(this);
+ * }
+ * </pre>
+ *
+ * <p>The {@link HashCodeExclude} annotation can be used to exclude fields from being
+ * used by the {@code reflectionHashCode} methods.</p>
+ *
+ * @since 1.0
+ */
+public class HashCodeBuilder implements Builder<Integer> {
+ /**
+ * The default initial value to use in reflection hash code building.
+ */
+ private static final int DEFAULT_INITIAL_VALUE = 17;
+
+ /**
+ * The default multiplier value to use in reflection hash code building.
+ */
+ private static final int DEFAULT_MULTIPLIER_VALUE = 37;
+
+ /**
+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
+ *
+ * @since 2.3
+ */
+ private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<>();
+
+ /*
+ * NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
+ * we are in the process of calculating.
+ *
+ * So we generate a one-to-one mapping from the original object to a new object.
+ *
+ * Now HashSet uses equals() to determine if two elements with the same hash code really
+ * are equal, so we also need to ensure that the replacement objects are only equal
+ * if the original objects are identical.
+ *
+ * The original implementation (2.4 and before) used the System.identityHashCode()
+ * method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
+ *
+ * We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
+ * to disambiguate the duplicate ids.
+ */
+
+ /**
+ * Returns the registry of objects being traversed by the reflection methods in the current thread.
+ *
+ * @return Set the registry of objects being traversed
+ * @since 2.3
+ */
+ static Set<IDKey> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * Returns {@code true} if the registry contains the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean {@code true} if the registry contains the given object.
+ * @since 2.3
+ */
+ static boolean isRegistered(final Object value) {
+ final Set<IDKey> registry = getRegistry();
+ return registry != null && registry.contains(new IDKey(value));
+ }
+
+ /**
+ * Appends the fields and values defined by the given object of the given {@link Class}.
+ *
+ * @param object
+ * the object to append details of
+ * @param clazz
+ * the class to append details of
+ * @param builder
+ * the builder to append to
+ * @param useTransients
+ * whether to use transient fields
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ */
+ private static void reflectionAppend(final Object object, final Class<?> clazz, final HashCodeBuilder builder, final boolean useTransients,
+ final String[] excludeFields) {
+ if (isRegistered(object)) {
+ return;
+ }
+ try {
+ register(object);
+ // The elements in the returned array are not sorted and are not in any particular order.
+ final Field[] fields = ArraySorter.sort(clazz.getDeclaredFields(), Comparator.comparing(Field::getName));
+ AccessibleObject.setAccessible(fields, true);
+ for (final Field field : fields) {
+ if (!ArrayUtils.contains(excludeFields, field.getName())
+ && !field.getName().contains("$")
+ && (useTransients || !Modifier.isTransient(field.getModifiers()))
+ && !Modifier.isStatic(field.getModifiers())
+ && !field.isAnnotationPresent(HashCodeExclude.class)) {
+ try {
+ final Object fieldValue = field.get(object);
+ builder.append(fieldValue);
+ } 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 InternalError("Unexpected IllegalAccessException");
+ }
+ }
+ }
+ } finally {
+ unregister(object);
+ }
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * {@link Object}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value. This will be the returned
+ * value if no fields are found to include in the hash code
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ *
+ * @see HashCodeExclude
+ */
+ public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value. This will be the returned
+ * value if no fields are found to include in the hash code
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ *
+ * @see HashCodeExclude
+ */
+ public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object,
+ final boolean testTransients) {
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be included up to and including the specified
+ * superclass. A null superclass is treated as java.lang.Object.
+ * </p>
+ *
+ * <p>
+ * Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital. Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param <T>
+ * the type of the object involved
+ * @param initialNonZeroOddNumber
+ * a non-zero, odd number used as the initial value. This will be the returned
+ * value if no fields are found to include in the hash code
+ * @param multiplierNonZeroOddNumber
+ * a non-zero, odd number used as the multiplier
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @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 use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ * @throws IllegalArgumentException
+ * if the number is zero or even
+ *
+ * @see HashCodeExclude
+ * @since 2.0
+ */
+ public static <T> int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final T object,
+ final boolean testTransients, final Class<? super T> reflectUpToClass, final String... excludeFields) {
+ Objects.requireNonNull(object, "object");
+ final HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
+ Class<?> clazz = object.getClass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
+ clazz = clazz.getSuperclass();
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
+ }
+ return builder.toHashCode();
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included. If no fields are found to include
+ * in the hash code, the result of this method will be constant.
+ * </p>
+ *
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @param testTransients
+ * whether to include transient fields
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is {@code null}
+ *
+ * @see HashCodeExclude
+ */
+ public static int reflectionHashCode(final Object object, final boolean testTransients) {
+ return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object,
+ testTransients, null);
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * {@link Object}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included. If no fields are found to include
+ * in the hash code, the result of this method will be constant.
+ * </p>
+ *
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @param excludeFields
+ * Collection of String field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is {@code null}
+ *
+ * @see HashCodeExclude
+ */
+ public static int reflectionHashCode(final Object object, final Collection<String> excludeFields) {
+ return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
+ }
+
+ /**
+ * Uses reflection to build a valid hash code from the fields of {@code object}.
+ *
+ * <p>
+ * This constructor uses two hard coded choices for the constants needed to build a hash code.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be used, as they are likely derived fields, and not part of the value of the
+ * {@link Object}.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be tested. Superclass fields will be included. If no fields are found to include
+ * in the hash code, the result of this method will be constant.
+ * </p>
+ *
+ * @param object
+ * the Object to create a {@code hashCode} for
+ * @param excludeFields
+ * array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException
+ * if the object is {@code null}
+ *
+ * @see HashCodeExclude
+ */
+ public static int reflectionHashCode(final Object object, final String... excludeFields) {
+ return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object, false,
+ null, excludeFields);
+ }
+
+ /**
+ * Registers the given object. Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value
+ * The object to register.
+ */
+ private static void register(final Object value) {
+ Set<IDKey> registry = getRegistry();
+ if (registry == null) {
+ registry = new HashSet<>();
+ REGISTRY.set(registry);
+ }
+ registry.add(new IDKey(value));
+ }
+
+ /**
+ * Unregisters the given object.
+ *
+ * <p>
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value
+ * The object to unregister.
+ * @since 2.3
+ */
+ private static void unregister(final Object value) {
+ final Set<IDKey> registry = getRegistry();
+ if (registry != null) {
+ registry.remove(new IDKey(value));
+ if (registry.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+
+ /**
+ * Constant to use in building the hashCode.
+ */
+ private final int iConstant;
+
+ /**
+ * Running total of the hashCode.
+ */
+ private int iTotal;
+
+ /**
+ * Uses two hard coded choices for the constants needed to build a {@code hashCode}.
+ *
+ */
+ public HashCodeBuilder() {
+ iConstant = 37;
+ iTotal = 17;
+ }
+
+ /**
+ * Two randomly chosen, odd numbers must be passed in. Ideally these should be different for each class,
+ * however this is not vital.
+ *
+ * <p>
+ * Prime numbers are preferred, especially for the multiplier.
+ * </p>
+ *
+ * @param initialOddNumber
+ * an odd number used as the initial value
+ * @param multiplierOddNumber
+ * an odd number used as the multiplier
+ * @throws IllegalArgumentException
+ * if the number is even
+ */
+ public HashCodeBuilder(final int initialOddNumber, final int multiplierOddNumber) {
+ Validate.isTrue(initialOddNumber % 2 != 0, "HashCodeBuilder requires an odd initial value");
+ Validate.isTrue(multiplierOddNumber % 2 != 0, "HashCodeBuilder requires an odd multiplier");
+ iConstant = multiplierOddNumber;
+ iTotal = initialOddNumber;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code boolean}.
+ *
+ * <p>
+ * This adds {@code 1} when true, and {@code 0} when false to the {@code hashCode}.
+ * </p>
+ * <p>
+ * This is in contrast to the standard {@code java.lang.Boolean.hashCode} handling, which computes
+ * a {@code hashCode} value of {@code 1231} for {@code java.lang.Boolean} instances
+ * that represent {@code true} or {@code 1237} for {@code java.lang.Boolean} instances
+ * that represent {@code false}.
+ * </p>
+ * <p>
+ * This is in accordance with the <i>Effective Java</i> design.
+ * </p>
+ *
+ * @param value
+ * the boolean to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final boolean value) {
+ iTotal = iTotal * iConstant + (value ? 0 : 1);
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code boolean} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final boolean[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final boolean element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code byte}.
+ *
+ * @param value
+ * the byte to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final byte value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code byte} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final byte[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final byte element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code char}.
+ *
+ * @param value
+ * the char to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final char value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code char} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final char[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final char element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code double}.
+ *
+ * @param value
+ * the double to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final double value) {
+ return append(Double.doubleToLongBits(value));
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code double} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final double[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final double element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code float}.
+ *
+ * @param value
+ * the float to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final float value) {
+ iTotal = iTotal * iConstant + Float.floatToIntBits(value);
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code float} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final float[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final float element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for an {@code int}.
+ *
+ * @param value
+ * the int to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final int value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for an {@code int} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final int[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final int element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code long}.
+ *
+ * @param value
+ * the long to add to the {@code hashCode}
+ * @return this
+ */
+ // NOTE: This method uses >> and not >>> as Effective Java and
+ // Long.hashCode do. Ideally we should switch to >>> at
+ // some stage. There are backwards compat issues, so
+ // that will have to wait for the time being. cf LANG-342.
+ public HashCodeBuilder append(final long value) {
+ iTotal = iTotal * iConstant + (int) (value ^ value >> 32);
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code long} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final long[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final long element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for an {@link Object}.
+ *
+ * @param object
+ * the Object to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final Object object) {
+ if (object == null) {
+ iTotal = iTotal * iConstant;
+
+ } else if (ObjectUtils.isArray(object)) {
+ // factor out array case in order to keep method small enough
+ // to be inlined
+ appendArray(object);
+ } else {
+ iTotal = iTotal * iConstant + object.hashCode();
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for an {@link Object} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final Object[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final Object element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code short}.
+ *
+ * @param value
+ * the short to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final short value) {
+ iTotal = iTotal * iConstant + value;
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for a {@code short} array.
+ *
+ * @param array
+ * the array to add to the {@code hashCode}
+ * @return this
+ */
+ public HashCodeBuilder append(final short[] array) {
+ if (array == null) {
+ iTotal = iTotal * iConstant;
+ } else {
+ for (final short element : array) {
+ append(element);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Append a {@code hashCode} for an array.
+ *
+ * @param object
+ * the array to add to the {@code hashCode}
+ */
+ private void appendArray(final Object object) {
+ // 'Switch' on type of array, to dispatch to the correct handler
+ // This handles multidimensional arrays
+ if (object instanceof long[]) {
+ append((long[]) object);
+ } else if (object instanceof int[]) {
+ append((int[]) object);
+ } else if (object instanceof short[]) {
+ append((short[]) object);
+ } else if (object instanceof char[]) {
+ append((char[]) object);
+ } else if (object instanceof byte[]) {
+ append((byte[]) object);
+ } else if (object instanceof double[]) {
+ append((double[]) object);
+ } else if (object instanceof float[]) {
+ append((float[]) object);
+ } else if (object instanceof boolean[]) {
+ append((boolean[]) object);
+ } else {
+ // Not an array of primitives
+ append((Object[]) object);
+ }
+ }
+
+ /**
+ * Adds the result of super.hashCode() to this builder.
+ *
+ * @param superHashCode
+ * the result of calling {@code super.hashCode()}
+ * @return this HashCodeBuilder, used to chain calls.
+ * @since 2.0
+ */
+ public HashCodeBuilder appendSuper(final int superHashCode) {
+ iTotal = iTotal * iConstant + superHashCode;
+ return this;
+ }
+
+ /**
+ * Returns the computed {@code hashCode}.
+ *
+ * @return {@code hashCode} based on the fields appended
+ *
+ * @since 3.0
+ */
+ @Override
+ public Integer build() {
+ return Integer.valueOf(toHashCode());
+ }
+
+ /**
+ * Implements equals using the hash code.
+ *
+ * @since 3.13.0
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof HashCodeBuilder)) {
+ return false;
+ }
+ final HashCodeBuilder other = (HashCodeBuilder) obj;
+ return iTotal == other.iTotal;
+ }
+
+ /**
+ * The computed {@code hashCode} from toHashCode() is returned due to the likelihood
+ * of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for
+ * HashCodeBuilder itself is.
+ *
+ * @return {@code hashCode} based on the fields appended
+ * @since 2.5
+ */
+ @Override
+ public int hashCode() {
+ return toHashCode();
+ }
+
+ /**
+ * Returns the computed {@code hashCode}.
+ *
+ * @return {@code hashCode} based on the fields appended
+ */
+ public int toHashCode() {
+ return iTotal;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java b/src/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java
new file mode 100755
index 000000000..d1c3661da
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/HashCodeExclude.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to exclude a field from being used by
+ * the various {@code reflectionHashcode} methods defined on
+ * {@link HashCodeBuilder}.
+ *
+ * @since 3.5
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface HashCodeExclude {
+ // empty
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/IDKey.java b/src/main/java/org/apache/commons/lang3/builder/IDKey.java
new file mode 100644
index 000000000..64e8fc5b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/IDKey.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+// adapted from org.apache.axis.utils.IDKey
+
+/**
+ * Wrap an identity key (System.identityHashCode())
+ * so that an object can only be equal() to itself.
+ *
+ * This is necessary to disambiguate the occasional duplicate
+ * identityHashCodes that can occur.
+ */
+final class IDKey {
+ private final Object value;
+ private final int id;
+
+ /**
+ * Constructor for IDKey
+ * @param value The value
+ */
+ IDKey(final Object value) {
+ // This is the Object hash code
+ this.id = System.identityHashCode(value);
+ // There have been some cases (LANG-459) that return the
+ // same identity hash code for different objects. So
+ // the value is also added to disambiguate these cases.
+ this.value = value;
+ }
+
+ /**
+ * returns hash code - i.e. the system identity hashcode.
+ * @return the hashcode
+ */
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ /**
+ * checks if instances are equal
+ * @param other The other object to compare to
+ * @return if the instances are for the same object
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof IDKey)) {
+ return false;
+ }
+ final IDKey idKey = (IDKey) other;
+ if (id != idKey.id) {
+ return false;
+ }
+ // Note that identity equals is used.
+ return value == idKey.value;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java
new file mode 100644
index 000000000..443282cd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Works with {@link ToStringBuilder} to create a "deep" {@code toString}.
+ * But instead a single line like the {@link RecursiveToStringStyle} this creates a multiline String
+ * similar to the {@link ToStringStyle#MULTI_LINE_STYLE}.
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class Job {
+ * String title;
+ * ...
+ * }
+ *
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * Job job;
+ *
+ * ...
+ *
+ * public String toString() {
+ * return new ReflectionToStringBuilder(this, new MultilineRecursiveToStringStyle()).toString();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * This will produce a toString of the format:<br>
+ * <code>Person@7f54[ <br>
+ * &nbsp; name=Stephen, <br>
+ * &nbsp; age=29, <br>
+ * &nbsp; smoker=false, <br>
+ * &nbsp; job=Job@43cd2[ <br>
+ * &nbsp; &nbsp; title=Manager <br>
+ * &nbsp; ] <br>
+ * ]
+ * </code>
+ * </p>
+ *
+ * @since 3.4
+ */
+public class MultilineRecursiveToStringStyle extends RecursiveToStringStyle {
+
+ /**
+ * Required for serialization support.
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** Indenting of inner lines. */
+ private static final int INDENT = 2;
+
+ /** Current indenting. */
+ private int spaces = 2;
+
+ /**
+ * Constructor.
+ */
+ public MultilineRecursiveToStringStyle() {
+ 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) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+ @Override
+ protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+ @Override
+ protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+ @Override
+ protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+ @Override
+ protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+ @Override
+ protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) {
+ spaces += INDENT;
+ resetIndent();
+ super.appendDetail(buffer, fieldName, array);
+ spaces -= INDENT;
+ resetIndent();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java
new file mode 100644
index 000000000..01b5e350c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.util.Collection;
+
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Works with {@link ToStringBuilder} to create a "deep" {@code toString}.
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class Job {
+ * String title;
+ * ...
+ * }
+ *
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * Job job;
+ *
+ * ...
+ *
+ * public String toString() {
+ * return new ReflectionToStringBuilder(this, new RecursiveToStringStyle()).toString();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>This will produce a toString of the format:
+ * {@code Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]}</p>
+ *
+ * @since 3.2
+ */
+public class RecursiveToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ */
+ 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
+ * subclasses to filter specific classes.
+ *
+ * @param clazz
+ * The class to test.
+ * @return Whether or not to recursively format the given {@link Class}.
+ */
+ protected boolean accept(final Class<?> clazz) {
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java
new file mode 100644
index 000000000..ba1cdb360
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import static org.apache.commons.lang3.reflect.FieldUtils.readField;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+
+/**
+ * Assists in implementing {@link Diffable#diff(Object)} methods.
+ *
+ * <p>
+ * All non-static, non-transient fields (including inherited fields)
+ * of the objects to diff are discovered using reflection and compared
+ * for differences.
+ * </p>
+ *
+ * <p>
+ * To use this class, write code as follows:
+ * </p>
+ *
+ * <pre>
+ * public class Person implements Diffable&lt;Person&gt; {
+ * String name;
+ * int age;
+ * boolean smoker;
+ * ...
+ *
+ * public DiffResult diff(Person obj) {
+ * // No need for null check, as NullPointerException correct if obj is null
+ * return new ReflectionDiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ * .build();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * The {@link ToStringStyle} passed to the constructor is embedded in the
+ * returned {@link DiffResult} and influences the style of the
+ * {@code DiffResult.toString()} method. This style choice can be overridden by
+ * calling {@link DiffResult#toString(ToStringStyle)}.
+ * </p>
+ * @param <T>
+ * type of the left and right object to diff.
+ * @see Diffable
+ * @see Diff
+ * @see DiffResult
+ * @see ToStringStyle
+ * @since 3.6
+ */
+public class ReflectionDiffBuilder<T> implements Builder<DiffResult<T>> {
+
+ private final Object left;
+ private final Object right;
+ private final DiffBuilder<T> diffBuilder;
+
+ /**
+ * Constructs a builder for the specified objects with the specified style.
+ *
+ * <p>
+ * 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.
+ * </p>
+ * @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 IllegalArgumentException
+ * if {@code lhs} or {@code rhs} is {@code null}
+ */
+ public ReflectionDiffBuilder(final T lhs, final T rhs, final ToStringStyle style) {
+ this.left = lhs;
+ this.right = rhs;
+ diffBuilder = new DiffBuilder<>(lhs, rhs, style);
+ }
+
+ @Override
+ public DiffResult<T> 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(), readField(field, left, true),
+ readField(field, right, true));
+ } catch (final IllegalAccessException ex) {
+ //this can't happen. Would get a Security exception instead
+ //throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ return !Modifier.isStatic(field.getModifiers());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
new file mode 100644
index 000000000..8bb0d0e10
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java
@@ -0,0 +1,877 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArraySorter;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.stream.Streams;
+
+/**
+ * Assists in implementing {@link Object#toString()} methods using reflection.
+ *
+ * <p>
+ * This class uses reflection to determine the fields to append. Because these fields are usually private, the class
+ * uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to
+ * change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
+ * set up correctly.
+ * </p>
+ * <p>
+ * Using reflection to access (private) fields circumvents any synchronization protection guarding access to these
+ * fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use
+ * synchronization consistent with the class' lock management around the invocation of the method. Take special care to
+ * exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if
+ * modified while the toString method is executing.
+ * </p>
+ * <p>
+ * A typical invocation for this method would look like:
+ * </p>
+ * <pre>
+ * public String toString() {
+ * return ReflectionToStringBuilder.toString(this);
+ * }
+ * </pre>
+ * <p>
+ * You can also use the builder to debug 3rd party objects:
+ * </p>
+ * <pre>
+ * System.out.println(&quot;An object: &quot; + ReflectionToStringBuilder.toString(anObject));
+ * </pre>
+ * <p>
+ * A subclass can control field output by overriding the methods:
+ * </p>
+ * <ul>
+ * <li>{@link #accept(java.lang.reflect.Field)}</li>
+ * <li>{@link #getValue(java.lang.reflect.Field)}</li>
+ * </ul>
+ * <p>
+ * For example, this method does <i>not</i> include the {@code password} field in the returned {@link String}:
+ * </p>
+ * <pre>
+ * public String toString() {
+ * return (new ReflectionToStringBuilder(this) {
+ * protected boolean accept(Field f) {
+ * return super.accept(f) &amp;&amp; !f.getName().equals(&quot;password&quot;);
+ * }
+ * }).toString();
+ * }
+ * </pre>
+ * <p>
+ * Alternatively the {@link ToStringExclude} annotation can be used to exclude fields from being incorporated in the
+ * result.
+ * </p>
+ * <p>
+ * It is also possible to use the {@link ToStringSummary} annotation to output the summary information instead of the
+ * detailed information of a field.
+ * </p>
+ * <p>
+ * The exact format of the {@code toString} is determined by the {@link ToStringStyle} passed into the constructor.
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> the default {@link ToStringStyle} will only do a "shallow" formatting, i.e. composed objects are not
+ * further traversed. To get "deep" formatting, use an instance of {@link RecursiveToStringStyle}.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class ReflectionToStringBuilder extends ToStringBuilder {
+
+ /**
+ * Converts the given Collection into an array of Strings. The returned array does not contain {@code null}
+ * entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
+ * is {@code null}.
+ *
+ * @param collection
+ * The collection to convert
+ * @return A new array of Strings.
+ */
+ static String[] toNoNullStringArray(final Collection<String> collection) {
+ if (collection == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ return toNoNullStringArray(collection.toArray());
+ }
+
+ /**
+ * Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
+ * (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
+ * if an array element is {@code null}.
+ *
+ * @param array
+ * The array to check
+ * @return The given array or a new array without null.
+ */
+ static String[] toNoNullStringArray(final Object[] array) {
+ return Streams.nonNull(array).map(Objects::toString).toArray(String[]::new);
+ }
+
+ /**
+ * Builds a {@code toString} value using the default {@link ToStringStyle} through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ */
+ public static String toString(final Object object) {
+ return toString(object, null, false, false, null);
+ }
+
+ /**
+ * Builds a {@code toString} value through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Transient members will be not be included, as they are likely derived. Static fields will not be included.
+ * Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is {@code null}, the default {@link ToStringStyle} is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object or {@link ToStringStyle} is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ */
+ public static String toString(final Object object, final ToStringStyle style) {
+ return toString(object, style, false, false, null);
+ }
+
+ /**
+ * Builds a {@code toString} value through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputTransients} is {@code true}, transient members will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is {@code null}, the default {@link ToStringStyle} is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ */
+ public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients) {
+ return toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ * Builds a {@code toString} value through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputTransients} is {@code true}, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are
+ * ignored.
+ * </p>
+ *
+ * <p>
+ * Static fields will not be included. Superclass fields will be appended.
+ * </p>
+ *
+ * <p>
+ * If the style is {@code null}, the default {@link ToStringStyle} is used.
+ * </p>
+ *
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ * @since 2.1
+ */
+ public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients, final boolean outputStatics) {
+ return toString(object, style, outputTransients, outputStatics, null);
+ }
+
+ /**
+ * Builds a {@code toString} value through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputTransients} is {@code true}, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are
+ * ignored.
+ * </p>
+ *
+ * <p>
+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
+ * {@code java.lang.Object}.
+ * </p>
+ *
+ * <p>
+ * If the style is {@code null}, the default {@link ToStringStyle} is used.
+ * </p>
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @param excludeNullValues
+ * whether to exclude fields whose values are null
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be {@code null}
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ * @since 3.6
+ */
+ public static <T> String toString(
+ final T object, final ToStringStyle style, final boolean outputTransients,
+ final boolean outputStatics, final boolean excludeNullValues, final Class<? super T> reflectUpToClass) {
+ return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNullValues)
+ .toString();
+ }
+
+ /**
+ * Builds a {@code toString} value through reflection.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputTransients} is {@code true}, transient fields will be output, otherwise they
+ * are ignored, as they are likely derived fields, and not part of the value of the Object.
+ * </p>
+ *
+ * <p>
+ * If the {@code outputStatics} is {@code true}, static fields will be output, otherwise they are
+ * ignored.
+ * </p>
+ *
+ * <p>
+ * Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
+ * {@code java.lang.Object}.
+ * </p>
+ *
+ * <p>
+ * If the style is {@code null}, the default {@link ToStringStyle} is used.
+ * </p>
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to be output
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be {@code null}
+ * @return the String result
+ * @throws IllegalArgumentException
+ * if the Object is {@code null}
+ *
+ * @see ToStringExclude
+ * @see ToStringSummary
+ * @since 2.1
+ */
+ public static <T> String toString(
+ final T object, final ToStringStyle style, final boolean outputTransients,
+ final boolean outputStatics, final Class<? super T> reflectUpToClass) {
+ return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
+ .toString();
+ }
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude. Null excludes nothing.
+ * @return The toString value.
+ */
+ public static String toStringExclude(final Object object, final Collection<String> excludeFieldNames) {
+ return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
+ }
+
+ /**
+ * Builds a String for a toString method excluding the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param excludeFieldNames
+ * The field names to exclude
+ * @return The toString value.
+ */
+ public static String toStringExclude(final Object object, final String... excludeFieldNames) {
+ return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
+ }
+
+
+ /**
+ * Builds a String for a toString method including the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param includeFieldNames
+ * {@code null} or empty means all fields are included. All fields are included by default. This method will override the default behavior.
+ * @return The toString value.
+ * @since 3.13.0
+ */
+ public static String toStringInclude(final Object object, final Collection<String> includeFieldNames) {
+ return toStringInclude(object, toNoNullStringArray(includeFieldNames));
+ }
+
+ /**
+ * Builds a String for a toString method including the given field names.
+ *
+ * @param object
+ * The object to "toString".
+ * @param includeFieldNames
+ * The field names to include. {@code null} or empty means all fields are included. All fields are included by default. This method will override the default
+ * behavior.
+ * @return The toString value.
+ * @since 3.13.0
+ */
+ public static String toStringInclude(final Object object, final String... includeFieldNames) {
+ return new ReflectionToStringBuilder(object).setIncludeFieldNames(includeFieldNames).toString();
+ }
+
+ /**
+ * Whether or not to append static fields.
+ */
+ private boolean appendStatics;
+
+ /**
+ * Whether or not to append transient fields.
+ */
+ private boolean appendTransients;
+
+ /**
+ * Whether or not to append fields that are null.
+ */
+ private boolean excludeNullValues;
+
+ /**
+ * Which field names to exclude from output. Intended for fields like {@code "password"}.
+ *
+ * @since 3.0 this is protected instead of private
+ */
+ protected String[] excludeFieldNames;
+
+ /**
+ * Field names that will be included in the output. All fields are included by default.
+ *
+ * @since 3.13.0
+ */
+ protected String[] includeFieldNames;
+
+ /**
+ * The last super class to stop appending fields for.
+ */
+ private Class<?> upToClass;
+
+ /**
+ * Constructs a new instance.
+ *
+ * <p>
+ * This constructor outputs using the default style set with {@code setDefaultStyle}.
+ * </p>
+ *
+ * @param object
+ * the Object to build a {@code toString} for, must not be {@code null}
+ * @throws IllegalArgumentException
+ * if the Object passed in is {@code null}
+ */
+ public ReflectionToStringBuilder(final Object object) {
+ super(Objects.requireNonNull(object, "obj"));
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * <p>
+ * If the style is {@code null}, the default style is used.
+ * </p>
+ *
+ * @param object
+ * the Object to build a {@code toString} for, must not be {@code null}
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @throws IllegalArgumentException
+ * if the Object passed in is {@code null}
+ */
+ public ReflectionToStringBuilder(final Object object, final ToStringStyle style) {
+ super(Objects.requireNonNull(object, "obj"), style);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * <p>
+ * If the style is {@code null}, the default style is used.
+ * </p>
+ *
+ * <p>
+ * If the buffer is {@code null}, a new one is created.
+ * </p>
+ *
+ * @param object
+ * the Object to build a {@code toString} for
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param buffer
+ * the {@link StringBuffer} to populate, may be {@code null}
+ * @throws IllegalArgumentException
+ * if the Object passed in is {@code null}
+ */
+ public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) {
+ super(Objects.requireNonNull(object, "obj"), style, buffer);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to build a {@code toString} for
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param buffer
+ * the {@link StringBuffer} to populate, may be {@code null}
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @since 2.1
+ */
+ public <T> ReflectionToStringBuilder(
+ final T object, final ToStringStyle style, final StringBuffer buffer,
+ final Class<? super T> reflectUpToClass, final boolean outputTransients, final boolean outputStatics) {
+ super(Objects.requireNonNull(object, "obj"), style, buffer);
+ this.setUpToClass(reflectUpToClass);
+ this.setAppendTransients(outputTransients);
+ this.setAppendStatics(outputStatics);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param <T>
+ * the type of the object
+ * @param object
+ * the Object to build a {@code toString} for
+ * @param style
+ * the style of the {@code toString} to create, may be {@code null}
+ * @param buffer
+ * the {@link StringBuffer} to populate, may be {@code null}
+ * @param reflectUpToClass
+ * the superclass to reflect up to (inclusive), may be {@code null}
+ * @param outputTransients
+ * whether to include transient fields
+ * @param outputStatics
+ * whether to include static fields
+ * @param excludeNullValues
+ * whether to exclude fields who value is null
+ * @since 3.6
+ */
+ public <T> ReflectionToStringBuilder(
+ final T object, final ToStringStyle style, final StringBuffer buffer,
+ final Class<? super T> reflectUpToClass, final boolean outputTransients, final boolean outputStatics,
+ final boolean excludeNullValues) {
+ super(Objects.requireNonNull(object, "obj"), style, buffer);
+ this.setUpToClass(reflectUpToClass);
+ this.setAppendTransients(outputTransients);
+ this.setAppendStatics(outputStatics);
+ this.setExcludeNullValues(excludeNullValues);
+ }
+
+ /**
+ * Returns whether or not to append the given {@link Field}.
+ * <ul>
+ * <li>Transient fields are appended only if {@link #isAppendTransients()} returns {@code true}.
+ * <li>Static fields are appended only if {@link #isAppendStatics()} returns {@code true}.
+ * <li>Inner class fields are not appended.</li>
+ * </ul>
+ *
+ * @param field
+ * The Field to test.
+ * @return Whether or not to append the given {@link Field}.
+ */
+ protected boolean accept(final Field field) {
+ if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
+ // Reject field from inner class.
+ return false;
+ }
+ if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
+ // Reject transient fields.
+ return false;
+ }
+ if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
+ // Reject static fields.
+ return false;
+ }
+
+ if (this.excludeFieldNames != null
+ && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
+ // Reject fields from the getExcludeFieldNames list.
+ return false;
+ }
+
+ if (ArrayUtils.isNotEmpty(includeFieldNames)) {
+ // Accept fields from the getIncludeFieldNames list. {@code null} or empty means all fields are included. All fields are included by default.
+ return Arrays.binarySearch(this.includeFieldNames, field.getName()) >= 0;
+ }
+
+ return !field.isAnnotationPresent(ToStringExclude.class);
+ }
+
+ /**
+ * Appends the fields and values defined by the given object of the given Class.
+ *
+ * <p>
+ * If a cycle is detected as an object is &quot;toString()'ed&quot;, such an object is rendered as if
+ * {@code Object.toString()} had been called and not implemented by the object.
+ * </p>
+ *
+ * @param clazz
+ * The class of object parameter
+ */
+ protected void appendFieldsIn(final Class<?> clazz) {
+ if (clazz.isArray()) {
+ this.reflectionAppendArray(this.getObject());
+ return;
+ }
+ // The elements in the returned array are not sorted and are not in any particular order.
+ final Field[] fields = ArraySorter.sort(clazz.getDeclaredFields(), Comparator.comparing(Field::getName));
+ AccessibleObject.setAccessible(fields, true);
+ for (final Field field : fields) {
+ final String fieldName = field.getName();
+ if (this.accept(field)) {
+ try {
+ // Warning: Field.get(Object) creates wrappers objects for primitive types.
+ final Object fieldValue = this.getValue(field);
+ if (!excludeNullValues || fieldValue != null) {
+ this.append(fieldName, fieldValue, !field.isAnnotationPresent(ToStringSummary.class));
+ }
+ } catch (final IllegalAccessException ex) {
+ // this can't happen. Would get a Security exception instead
+ // throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the excludeFieldNames.
+ *
+ * @return the excludeFieldNames.
+ */
+ public String[] getExcludeFieldNames() {
+ return this.excludeFieldNames.clone();
+ }
+
+ /**
+ * Gets the includeFieldNames
+ *
+ * @return the includeFieldNames.
+ * @since 3.13.0
+ */
+ public String[] getIncludeFieldNames() {
+ return this.includeFieldNames.clone();
+ }
+
+ /**
+ * Gets the last super class to stop appending fields for.
+ *
+ * @return The last super class to stop appending fields for.
+ */
+ public Class<?> getUpToClass() {
+ return this.upToClass;
+ }
+
+ /**
+ * Calls {@code java.lang.reflect.Field.get(Object)}.
+ *
+ * @param field
+ * The Field to query.
+ * @return The Object from the given Field.
+ *
+ * @throws IllegalArgumentException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ * @throws IllegalAccessException
+ * see {@link java.lang.reflect.Field#get(Object)}
+ *
+ * @see java.lang.reflect.Field#get(Object)
+ */
+ protected Object getValue(final Field field) throws IllegalAccessException {
+ return field.get(this.getObject());
+ }
+
+ /**
+ * Gets whether or not to append static fields.
+ *
+ * @return Whether or not to append static fields.
+ * @since 2.1
+ */
+ public boolean isAppendStatics() {
+ return this.appendStatics;
+ }
+
+ /**
+ * Gets whether or not to append transient fields.
+ *
+ * @return Whether or not to append transient fields.
+ */
+ public boolean isAppendTransients() {
+ return this.appendTransients;
+ }
+
+ /**
+ * Gets whether or not to append fields whose values are null.
+ *
+ * @return Whether or not to append fields whose values are null.
+ * @since 3.6
+ */
+ public boolean isExcludeNullValues() {
+ return this.excludeNullValues;
+ }
+
+ /**
+ * Appends to the {@code toString} an {@link Object} array.
+ *
+ * @param array
+ * the array to add to the {@code toString}
+ * @return this
+ */
+ public ReflectionToStringBuilder reflectionAppendArray(final Object array) {
+ this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
+ return this;
+ }
+
+ /**
+ * Sets whether or not to append static fields.
+ *
+ * @param appendStatics
+ * Whether or not to append static fields.
+ * @since 2.1
+ */
+ public void setAppendStatics(final boolean appendStatics) {
+ this.appendStatics = appendStatics;
+ }
+
+ /**
+ * Sets whether or not to append transient fields.
+ *
+ * @param appendTransients
+ * Whether or not to append transient fields.
+ */
+ public void setAppendTransients(final boolean appendTransients) {
+ this.appendTransients = appendTransients;
+ }
+
+ /**
+ * Sets the field names to exclude.
+ *
+ * @param excludeFieldNamesParam
+ * The excludeFieldNames to excluding from toString or {@code null}.
+ * @return {@code this}
+ */
+ public ReflectionToStringBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) {
+ if (excludeFieldNamesParam == null) {
+ this.excludeFieldNames = null;
+ } else {
+ // clone and remove nulls
+ this.excludeFieldNames = ArraySorter.sort(toNoNullStringArray(excludeFieldNamesParam));
+ }
+ return this;
+ }
+
+ /**
+ * Sets whether or not to append fields whose values are null.
+ *
+ * @param excludeNullValues
+ * Whether or not to append fields whose values are null.
+ * @since 3.6
+ */
+ public void setExcludeNullValues(final boolean excludeNullValues) {
+ this.excludeNullValues = excludeNullValues;
+ }
+
+ /**
+ * Sets the field names to include. {@code null} or empty means all fields are included. All fields are included by default. This method will override the default behavior.
+ *
+ * @param includeFieldNamesParam
+ * The includeFieldNames that must be on toString or {@code null}.
+ * @return {@code this}
+ * @since 3.13.0
+ */
+ public ReflectionToStringBuilder setIncludeFieldNames(final String... includeFieldNamesParam) {
+ if (includeFieldNamesParam == null) {
+ this.includeFieldNames = null;
+ } else {
+ // clone and remove nulls
+ this.includeFieldNames = ArraySorter.sort(toNoNullStringArray(includeFieldNamesParam));
+ }
+ return this;
+ }
+
+ /**
+ * Sets the last super class to stop appending fields for.
+ *
+ * @param clazz
+ * The last super class to stop appending fields for.
+ */
+ public void setUpToClass(final Class<?> clazz) {
+ if (clazz != null) {
+ final Object object = getObject();
+ if (object != null && !clazz.isInstance(object)) {
+ throw new IllegalArgumentException("Specified class is not a superclass of the object");
+ }
+ }
+ this.upToClass = clazz;
+ }
+
+ /**
+ * Gets the String built by this builder.
+ *
+ * @return the built string
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ return this.getStyle().getNullText();
+ }
+
+ validate();
+
+ Class<?> clazz = this.getObject().getClass();
+ this.appendFieldsIn(clazz);
+ while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
+ clazz = clazz.getSuperclass();
+ this.appendFieldsIn(clazz);
+ }
+ return super.toString();
+ }
+
+ /**
+ * Validates that include and exclude names do not intersect.
+ */
+ private void validate() {
+ if (ArrayUtils.containsAny(this.excludeFieldNames, (Object[]) this.includeFieldNames)) {
+ ToStringStyle.unregister(this.getObject());
+ throw new IllegalStateException("includeFieldNames and excludeFieldNames must not intersect");
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java
new file mode 100644
index 000000000..ba49f0b47
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/StandardToStringStyle.java
@@ -0,0 +1,520 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Works with {@link ToStringBuilder} to create a {@code toString}.
+ *
+ * <p>This class is intended to be used as a singleton.
+ * There is no need to instantiate a new style each time.
+ * Simply instantiate the class once, customize the values as required, and
+ * store the result in a public static final variable for the rest of the
+ * program to access.</p>
+ *
+ * @since 1.0
+ */
+public class StandardToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ */
+ 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.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arrayStart the new array start text
+ */
+ @Override
+ public void setArrayStart(final String arrayStart) {
+ super.setArrayStart(arrayStart);
+ }
+
+ /**
+ * Gets the array end text.
+ *
+ * @return the current array end text
+ */
+ @Override
+ public String getArrayEnd() {
+ return super.getArrayEnd();
+ }
+
+ /**
+ * Sets the array end text.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arrayEnd the new array end text
+ */
+ @Override
+ public void setArrayEnd(final String arrayEnd) {
+ super.setArrayEnd(arrayEnd);
+ }
+
+ /**
+ * Gets the array separator text.
+ *
+ * @return the current array separator text
+ */
+ @Override
+ public String getArraySeparator() {
+ return super.getArraySeparator();
+ }
+
+ /**
+ * Sets the array separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param arraySeparator the new array separator 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.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param contentStart the new content start text
+ */
+ @Override
+ public void setContentStart(final String contentStart) {
+ super.setContentStart(contentStart);
+ }
+
+ /**
+ * Gets the content end text.
+ *
+ * @return the current content end text
+ */
+ @Override
+ public String getContentEnd() {
+ return super.getContentEnd();
+ }
+
+ /**
+ * Sets the content end text.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param contentEnd the new content end text
+ */
+ @Override
+ public void setContentEnd(final String contentEnd) {
+ super.setContentEnd(contentEnd);
+ }
+
+ /**
+ * Gets the field name value separator text.
+ *
+ * @return the current field name value separator text
+ */
+ @Override
+ public String getFieldNameValueSeparator() {
+ return super.getFieldNameValueSeparator();
+ }
+
+ /**
+ * Sets the field name value separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param fieldNameValueSeparator the new field name value separator text
+ */
+ @Override
+ public void setFieldNameValueSeparator(final String fieldNameValueSeparator) {
+ super.setFieldNameValueSeparator(fieldNameValueSeparator);
+ }
+
+ /**
+ * Gets the field separator text.
+ *
+ * @return the current field separator text
+ */
+ @Override
+ public String getFieldSeparator() {
+ return super.getFieldSeparator();
+ }
+
+ /**
+ * Sets the field separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @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.
+ *
+ * @return the current text to output when {@code null} found
+ */
+ @Override
+ public String getNullText() {
+ return super.getNullText();
+ }
+
+ /**
+ * Sets the text to output when {@code null} found.
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param nullText the new text to output when {@code null} found
+ */
+ @Override
+ public void setNullText(final String nullText) {
+ super.setNullText(nullText);
+ }
+
+ /**
+ * Gets the text to output when a {@link Collection},
+ * {@link Map} or {@link Array} size is output.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @return the current start of size text
+ */
+ @Override
+ public String getSizeStartText() {
+ return super.getSizeStartText();
+ }
+
+ /**
+ * Sets the start text to output when a {@link Collection},
+ * {@link Map} or {@link Array} size is output.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @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.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of size text
+ */
+ @Override
+ public String getSizeEndText() {
+ return super.getSizeEndText();
+ }
+
+ /**
+ * Sets the end text to output when a {@link Collection},
+ * {@link Map} or {@link Array} size is output.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted
+ * to an empty String.</p>
+ *
+ * @param sizeEndText the new end of size text
+ */
+ @Override
+ public void setSizeEndText(final String sizeEndText) {
+ super.setSizeEndText(sizeEndText);
+ }
+
+ /**
+ * Gets the start text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @return the current start of summary text
+ */
+ @Override
+ public String getSummaryObjectStartText() {
+ return super.getSummaryObjectStartText();
+ }
+
+ /**
+ * Sets the start text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectStartText the new start of summary text
+ */
+ @Override
+ public void setSummaryObjectStartText(final String summaryObjectStartText) {
+ super.setSummaryObjectStartText(summaryObjectStartText);
+ }
+
+ /**
+ * Gets the end text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of summary text
+ */
+ @Override
+ public String getSummaryObjectEndText() {
+ return super.getSummaryObjectEndText();
+ }
+
+ /**
+ * Sets the end text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectEndText the new end of summary text
+ */
+ @Override
+ public void setSummaryObjectEndText(final String summaryObjectEndText) {
+ super.setSummaryObjectEndText(summaryObjectEndText);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java
new file mode 100644
index 000000000..231d3fc7a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java
@@ -0,0 +1,1034 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Assists in implementing {@link Object#toString()} methods.
+ *
+ * <p>This class enables a good and consistent {@code toString()} to be built for any
+ * class or object. This class aims to simplify the process by:</p>
+ * <ul>
+ * <li>allowing field names</li>
+ * <li>handling all types consistently</li>
+ * <li>handling nulls consistently</li>
+ * <li>outputting arrays and multi-dimensional arrays</li>
+ * <li>enabling the detail level to be controlled for Objects and Collections</li>
+ * <li>handling class hierarchies</li>
+ * </ul>
+ *
+ * <p>To use this class write code as follows:</p>
+ *
+ * <pre>
+ * public class Person {
+ * String name;
+ * int age;
+ * boolean smoker;
+ *
+ * ...
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * append("name", name).
+ * append("age", age).
+ * append("smoker", smoker).
+ * toString();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>This will produce a toString of the format:
+ * {@code Person@7f54[name=Stephen,age=29,smoker=false]}</p>
+ *
+ * <p>To add the superclass {@code toString}, use {@link #appendSuper}.
+ * To append the {@code toString} from an object that is delegated
+ * to (or any other object), use {@link #appendToString}.</p>
+ *
+ * <p>Alternatively, there is a method that uses reflection to determine
+ * the fields to test. Because these fields are usually private, the method,
+ * {@code reflectionToString}, uses {@code AccessibleObject.setAccessible} to
+ * change the visibility of the fields. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly. It is also
+ * slower than testing explicitly.</p>
+ *
+ * <p>A typical invocation for this method would look like:</p>
+ *
+ * <pre>
+ * public String toString() {
+ * return ToStringBuilder.reflectionToString(this);
+ * }
+ * </pre>
+ *
+ * <p>You can also use the builder to debug 3rd party objects:</p>
+ *
+ * <pre>
+ * System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
+ * </pre>
+ *
+ * <p>The exact format of the {@code toString} is determined by
+ * the {@link ToStringStyle} passed into the constructor.</p>
+ *
+ * @since 1.0
+ */
+public class ToStringBuilder implements Builder<String> {
+
+ /**
+ * The default style of output to use, not null.
+ */
+ private static volatile ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE;
+
+ /**
+ * Gets the default {@link ToStringStyle} to use.
+ *
+ * <p>This method gets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a {@link ToStringStyle} to the constructor instead
+ * of using this global default.</p>
+ *
+ * <p>This method can be used from multiple threads.
+ * Internally, a {@code volatile} variable is used to provide the guarantee
+ * that the latest value set using {@link #setDefaultStyle} is the value returned.
+ * It is strongly recommended that the default style is only changed during application startup.</p>
+ *
+ * <p>One reason for changing the default could be to have a verbose style during
+ * development and a compact style in production.</p>
+ *
+ * @return the default {@link ToStringStyle}, never null
+ */
+ public static ToStringStyle getDefaultStyle() {
+ return defaultStyle;
+ }
+
+ /**
+ * Sets the default {@link ToStringStyle} to use.
+ *
+ * <p>This method sets a singleton default value, typically for the whole JVM.
+ * Changing this default should generally only be done during application startup.
+ * It is recommended to pass a {@link ToStringStyle} to the constructor instead
+ * of changing this global default.</p>
+ *
+ * <p>This method is not intended for use from multiple threads.
+ * Internally, a {@code volatile} variable is used to provide the guarantee
+ * that the latest value set is the value returned from {@link #getDefaultStyle}.</p>
+ *
+ * @param style the default {@link ToStringStyle}
+ * @throws IllegalArgumentException 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.
+ *
+ * @param object the Object to be output
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object)
+ */
+ public static String reflectionToString(final Object object) {
+ return ReflectionToStringBuilder.toString(object);
+ }
+
+ /**
+ * Uses {@link ReflectionToStringBuilder} to generate a
+ * {@code toString} for the specified object.
+ *
+ * @param object the Object to be output
+ * @param style the style of the {@code toString} to create, may be {@code null}
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle)
+ */
+ public static String reflectionToString(final Object object, final ToStringStyle style) {
+ return ReflectionToStringBuilder.toString(object, style);
+ }
+
+ /**
+ * Uses {@link ReflectionToStringBuilder} to generate a
+ * {@code toString} for the specified object.
+ *
+ * @param object the Object to be output
+ * @param style the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients whether to include transient fields
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean)
+ */
+ public static String reflectionToString(final Object object, final ToStringStyle style, final boolean outputTransients) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, null);
+ }
+
+ /**
+ * Uses {@link ReflectionToStringBuilder} to generate a
+ * {@code toString} for the specified object.
+ *
+ * @param <T> the type of the object
+ * @param object the Object to be output
+ * @param style the style of the {@code toString} to create, may be {@code null}
+ * @param outputTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive), may be {@code null}
+ * @return the String result
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,boolean,Class)
+ * @since 2.0
+ */
+ public static <T> String reflectionToString(
+ final T object,
+ final ToStringStyle style,
+ final boolean outputTransients,
+ final Class<? super T> reflectUpToClass) {
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, false, reflectUpToClass);
+ }
+
+ /**
+ * Current toString buffer, not null.
+ */
+ private final StringBuffer buffer;
+ /**
+ * The object being output, may be null.
+ */
+ private final Object object;
+ /**
+ * The style of output to use, not null.
+ */
+ private final ToStringStyle style;
+
+ /**
+ * Constructs a builder for the specified object using the default output style.
+ *
+ * <p>This default style is obtained from {@link #getDefaultStyle()}.</p>
+ *
+ * @param object the Object to build a {@code toString} for, not recommended to be null
+ */
+ public ToStringBuilder(final Object object) {
+ this(object, null, null);
+ }
+
+ /**
+ * Constructs a builder for the specified object using the defined output style.
+ *
+ * <p>If the style is {@code null}, the default style is used.</p>
+ *
+ * @param object the Object to build a {@code toString} for, not recommended to be null
+ * @param style the style of the {@code toString} to create, null uses the default style
+ */
+ public ToStringBuilder(final Object object, final ToStringStyle style) {
+ this(object, style, null);
+ }
+
+ /**
+ * Constructs a builder for the specified object.
+ *
+ * <p>If the style is {@code null}, the default style is used.</p>
+ *
+ * <p>If the buffer is {@code null}, a new one is created.</p>
+ *
+ * @param object the Object to build a {@code toString} for, not recommended to be null
+ * @param style the style of the {@code toString} to create, null uses the default style
+ * @param buffer the {@link StringBuffer} to populate, may be null
+ */
+ public ToStringBuilder(final Object object, ToStringStyle style, StringBuffer buffer) {
+ if (style == null) {
+ style = getDefaultStyle();
+ }
+ if (buffer == null) {
+ buffer = new StringBuffer(512);
+ }
+ this.buffer = buffer;
+ this.style = style;
+ this.object = object;
+
+ style.appendStart(buffer, object);
+ }
+
+ /**
+ * Append to the {@code toString} a {@code boolean}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final boolean value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code boolean}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final boolean[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code byte}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final byte value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code byte}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final byte[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code char}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final char value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code char}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final char[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code double}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final double value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code double}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final double[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code float}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final float value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code float}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final float[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code int}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final int value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code int}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final int[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code long}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final long value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code long}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final long[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * value.
+ *
+ * @param obj the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final Object obj) {
+ style.append(buffer, null, obj, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final Object[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code short}
+ * value.
+ *
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final short value) {
+ style.append(buffer, null, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code short}
+ * array.
+ *
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final short[] array) {
+ style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code boolean}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final boolean value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code boolean}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code hashCode}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final boolean[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code boolean}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final boolean[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code byte}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final byte value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code byte} array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final byte[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code byte}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final byte[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code char}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final char value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code char}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final char[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code char}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final char[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code double}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final double value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code double}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final double[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code double}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final double[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code float}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final float value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code float}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final float[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code float}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final float[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code int}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final int value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code int}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final int[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code int}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final int[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code long}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final long value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code long}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final long[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code long}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final long[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final Object obj) {
+ style.append(buffer, fieldName, obj, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param obj the value to add to the {@code toString}
+ * @param fullDetail {@code true} for detail,
+ * {@code false} for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final Object obj, final boolean fullDetail) {
+ style.append(buffer, fieldName, obj, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final Object[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@link Object}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.</p>
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final Object[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} an {@code short}
+ * value.
+ *
+ * @param fieldName the field name
+ * @param value the value to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final short value) {
+ style.append(buffer, fieldName, value);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code short}
+ * array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final short[] array) {
+ style.append(buffer, fieldName, array, null);
+ return this;
+ }
+
+ /**
+ * Append to the {@code toString} a {@code short}
+ * array.
+ *
+ * <p>A boolean parameter controls the level of detail to show.
+ * Setting {@code true} will output the array in full. Setting
+ * {@code false} will output a summary, typically the size of
+ * the array.
+ *
+ * @param fieldName the field name
+ * @param array the array to add to the {@code toString}
+ * @param fullDetail {@code true} for detail, {@code false}
+ * for summary info
+ * @return this
+ */
+ public ToStringBuilder append(final String fieldName, final short[] array, final boolean fullDetail) {
+ style.append(buffer, fieldName, array, Boolean.valueOf(fullDetail));
+ return this;
+ }
+
+ /**
+ * Appends with the same format as the default <code>Object toString()
+ * </code> method. Appends the class name followed by
+ * {@link System#identityHashCode(Object)}.
+ *
+ * @param srcObject the {@link Object} whose class name and id to output
+ * @return this
+ * @throws NullPointerException if {@code srcObject} is {@code null}
+ * @since 2.0
+ */
+ public ToStringBuilder appendAsObjectToString(final Object srcObject) {
+ ObjectUtils.identityToString(this.getStringBuffer(), srcObject);
+ return this;
+ }
+
+ /**
+ * Append the {@code toString} from the superclass.
+ *
+ * <p>This method assumes that the superclass uses the same {@link ToStringStyle}
+ * as this one.</p>
+ *
+ * <p>If {@code superToString} is {@code null}, no change is made.</p>
+ *
+ * @param superToString the result of {@code super.toString()}
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendSuper(final String superToString) {
+ if (superToString != null) {
+ style.appendSuper(buffer, superToString);
+ }
+ return this;
+ }
+
+ /**
+ * Append the {@code toString} from another object.
+ *
+ * <p>This method is useful where a class delegates most of the implementation of
+ * its properties to another class. You can then call {@code toString()} on
+ * the other class and pass the result into this method.</p>
+ *
+ * <pre>
+ * private AnotherObject delegate;
+ * private String fieldInThisClass;
+ *
+ * public String toString() {
+ * return new ToStringBuilder(this).
+ * appendToString(delegate.toString()).
+ * append(fieldInThisClass).
+ * toString();
+ * }</pre>
+ *
+ * <p>This method assumes that the other object uses the same {@link ToStringStyle}
+ * as this one.</p>
+ *
+ * <p>If the {@code toString} is {@code null}, no change is made.</p>
+ *
+ * @param toString the result of {@code toString()} on another object
+ * @return this
+ * @since 2.0
+ */
+ public ToStringBuilder appendToString(final String toString) {
+ if (toString != null) {
+ style.appendToString(buffer, toString);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the {@link Object} being output.
+ *
+ * @return The object being output.
+ * @since 2.0
+ */
+ public Object getObject() {
+ return object;
+ }
+
+ /**
+ * Gets the {@link StringBuffer} being populated.
+ *
+ * @return the {@link StringBuffer} being populated
+ */
+ public StringBuffer getStringBuffer() {
+ return buffer;
+ }
+
+ /**
+ * Gets the {@link ToStringStyle} being used.
+ *
+ * @return the {@link ToStringStyle} being used
+ * @since 2.0
+ */
+ public ToStringStyle getStyle() {
+ return style;
+ }
+
+ /**
+ * Returns the built {@code toString}.
+ *
+ * <p>This method appends the end of data indicator, and can only be called once.
+ * Use {@link #getStringBuffer} to get the current string state.</p>
+ *
+ * <p>If the object is {@code null}, return the style's {@code nullText}</p>
+ *
+ * @return the String {@code toString}
+ */
+ @Override
+ public String toString() {
+ if (this.getObject() == null) {
+ this.getStringBuffer().append(this.getStyle().getNullText());
+ } else {
+ style.appendEnd(this.getStringBuffer(), this.getObject());
+ }
+ return this.getStringBuffer().toString();
+ }
+
+ /**
+ * Returns the String that was build as an object representation. The
+ * default implementation utilizes the {@link #toString()} implementation.
+ *
+ * @return the String {@code toString}
+ *
+ * @see #toString()
+ *
+ * @since 3.0
+ */
+ @Override
+ public String build() {
+ return toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringExclude.java b/src/main/java/org/apache/commons/lang3/builder/ToStringExclude.java
new file mode 100755
index 000000000..2b399f288
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ToStringExclude.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to exclude a field from being used by
+ * the {@link ReflectionToStringBuilder}.
+ *
+ * @since 3.5
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ToStringExclude {
+ // empty
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java
new file mode 100644
index 000000000..98ecc8fb6
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java
@@ -0,0 +1,2555 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.builder;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.WeakHashMap;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Controls {@link String} formatting for {@link ToStringBuilder}.
+ * The main public interface is always via {@link ToStringBuilder}.
+ *
+ * <p>These classes are intended to be used as <em>singletons</em>.
+ * There is no need to instantiate a new style each time. A program
+ * will generally use one of the predefined constants on this class.
+ * Alternatively, the {@link StandardToStringStyle} class can be used
+ * to set the individual settings. Thus most styles can be achieved
+ * without subclassing.</p>
+ *
+ * <p>If required, a subclass can override as many or as few of the
+ * methods as it requires. Each object type (from {@code boolean}
+ * to {@code long} to {@link Object} to {@code int[]}) has
+ * its own methods to output it. Most have two versions, detail and summary.
+ *
+ * <p>For example, the detail version of the array based methods will
+ * output the whole array, whereas the summary method will just output
+ * the array length.</p>
+ *
+ * <p>If you want to format the output of certain objects, such as dates, you
+ * must create a subclass and override a method.
+ * </p>
+ * <pre>
+ * public class MyStyle extends ToStringStyle {
+ * protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
+ * if (value instanceof Date) {
+ * value = new SimpleDateFormat("yyyy-MM-dd").format(value);
+ * }
+ * buffer.append(value);
+ * }
+ * }
+ * </pre>
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("deprecation") // StringEscapeUtils
+public abstract class ToStringStyle implements Serializable {
+
+ /**
+ * Serialization version ID.
+ */
+ private static final long serialVersionUID = -2587890625525655916L;
+
+ /**
+ * The default toString style. Using the {@code Person}
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person@182f0db[name=John Doe,age=33,smoker=false]
+ * </pre>
+ */
+ public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
+
+ /**
+ * The multi line toString style. Using the {@code Person}
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person@182f0db[
+ * name=John Doe
+ * age=33
+ * smoker=false
+ * ]
+ * </pre>
+ */
+ public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();
+
+ /**
+ * The no field names toString style. Using the
+ * {@code Person} example from {@link ToStringBuilder}, the output
+ * would look like this:
+ *
+ * <pre>
+ * Person@182f0db[John Doe,33,false]
+ * </pre>
+ */
+ public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle();
+
+ /**
+ * The short prefix toString style. Using the {@code Person} example
+ * from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * Person[name=John Doe,age=33,smoker=false]
+ * </pre>
+ *
+ * @since 2.1
+ */
+ public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle();
+
+ /**
+ * The simple toString style. Using the {@code Person}
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * John Doe,33,false
+ * </pre>
+ */
+ public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();
+
+ /**
+ * The no class name toString style. Using the {@code Person}
+ * example from {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * [name=John Doe,age=33,smoker=false]
+ * </pre>
+ *
+ * @since 3.4
+ */
+ public static final ToStringStyle NO_CLASS_NAME_STYLE = new NoClassNameToStringStyle();
+
+ /**
+ * The JSON toString style. Using the {@code Person} example from
+ * {@link ToStringBuilder}, the output would look like this:
+ *
+ * <pre>
+ * {"name": "John Doe", "age": 33, "smoker": true}
+ * </pre>
+ *
+ * <strong>Note:</strong> Since field names are mandatory in JSON, this
+ * ToStringStyle will throw an {@link UnsupportedOperationException} if no
+ * field name is passed in while appending. Furthermore This ToStringStyle
+ * will only generate valid JSON if referenced objects also produce JSON
+ * when calling {@code toString()} on them.
+ *
+ * @since 3.4
+ * @see <a href="https://www.json.org/">json.org</a>
+ */
+ public static final ToStringStyle JSON_STYLE = new JsonToStringStyle();
+
+ /**
+ * A registry of objects used by {@code reflectionToString} methods
+ * to detect cyclical object references and avoid infinite loops.
+ *
+ */
+ private static final ThreadLocal<WeakHashMap<Object, Object>> REGISTRY = new ThreadLocal<>();
+ /*
+ * Note that objects of this class are generally shared between threads, so
+ * an instance variable would not be suitable here.
+ *
+ * In normal use the registry should always be left empty, because the caller
+ * should call toString() which will clean up.
+ *
+ * See LANG-792
+ */
+
+ /**
+ * Returns the registry of objects being traversed by the {@code reflectionToString}
+ * methods in the current thread.
+ *
+ * @return Set the registry of objects being traversed
+ */
+ public static Map<Object, Object> getRegistry() {
+ return REGISTRY.get();
+ }
+
+ /**
+ * Returns {@code true} if the registry contains the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value
+ * The object to lookup in the registry.
+ * @return boolean {@code true} if the registry contains the given
+ * object.
+ */
+ static boolean isRegistered(final Object value) {
+ final Map<Object, Object> m = getRegistry();
+ return m != null && m.containsKey(value);
+ }
+
+ /**
+ * Registers the given object. Used by the reflection methods to avoid
+ * infinite loops.
+ *
+ * @param value
+ * The object to register.
+ */
+ static void register(final Object value) {
+ if (value != null) {
+ final Map<Object, Object> m = getRegistry();
+ if (m == null) {
+ REGISTRY.set(new WeakHashMap<>());
+ }
+ getRegistry().put(value, null);
+ }
+ }
+
+ /**
+ * Unregisters the given object.
+ *
+ * <p>
+ * Used by the reflection methods to avoid infinite loops.
+ * </p>
+ *
+ * @param value
+ * The object to unregister.
+ */
+ static void unregister(final Object value) {
+ if (value != null) {
+ final Map<Object, Object> m = getRegistry();
+ if (m != null) {
+ m.remove(value);
+ if (m.isEmpty()) {
+ REGISTRY.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether to use the field names, the default is {@code true}.
+ */
+ private boolean useFieldNames = true;
+
+ /**
+ * Whether to use the class name, the default is {@code true}.
+ */
+ private boolean useClassName = true;
+
+ /**
+ * Whether to use short class names, the default is {@code false}.
+ */
+ private boolean useShortClassName;
+
+ /**
+ * Whether to use the identity hash code, the default is {@code true}.
+ */
+ private boolean useIdentityHashCode = true;
+
+ /**
+ * The content start {@code '['}.
+ */
+ private String contentStart = "[";
+
+ /**
+ * The content end {@code ']'}.
+ */
+ private String contentEnd = "]";
+
+ /**
+ * The field name value separator {@code '='}.
+ */
+ private String fieldNameValueSeparator = "=";
+
+ /**
+ * Whether the field separator should be added before any other fields.
+ */
+ private boolean fieldSeparatorAtStart;
+
+ /**
+ * Whether the field separator should be added after any other fields.
+ */
+ private boolean fieldSeparatorAtEnd;
+
+ /**
+ * The field separator {@code ','}.
+ */
+ private String fieldSeparator = ",";
+
+ /**
+ * The array start <code>'{'</code>.
+ */
+ private String arrayStart = "{";
+
+ /**
+ * The array separator {@code ','}.
+ */
+ private String arraySeparator = ",";
+
+ /**
+ * The detail for array content.
+ */
+ private boolean arrayContentDetail = true;
+
+ /**
+ * The array end {@code '}'}.
+ */
+ private String arrayEnd = "}";
+
+ /**
+ * The value to use when fullDetail is {@code null},
+ * the default value is {@code true}.
+ */
+ private boolean defaultFullDetail = true;
+
+ /**
+ * The {@code null} text {@code '&lt;null&gt;'}.
+ */
+ private String nullText = "<null>";
+
+ /**
+ * The summary size text start {@code '&lt;size'}.
+ */
+ private String sizeStartText = "<size=";
+
+ /**
+ * The summary size text start {@code '&gt;'}.
+ */
+ private String sizeEndText = ">";
+
+ /**
+ * The summary object text start {@code '&lt;'}.
+ */
+ private String summaryObjectStartText = "<";
+
+ /**
+ * The summary object text start {@code '&gt;'}.
+ */
+ private String summaryObjectEndText = ">";
+
+ /**
+ * Constructor.
+ */
+ protected ToStringStyle() {
+ }
+
+ /**
+ * Appends to the {@code toString} the superclass toString.
+ * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p>
+ *
+ * <p>A {@code null} {@code superToString} is ignored.</p>
+ *
+ * @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.
+ * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p>
+ *
+ * <p>A {@code null} {@code toString} is ignored.</p>
+ *
+ * @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);
+ }
+ }
+ }
+
+ /**
+ * Appends to the {@code toString} the start of data indicator.
+ *
+ * @param buffer the {@link StringBuffer} to populate
+ * @param object the {@link Object} to build a {@code toString} for
+ */
+ 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} 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);
+ }
+
+ /**
+ * Remove the last field separator from the buffer.
+ *
+ * @param buffer the {@link StringBuffer} to populate
+ * @since 2.0
+ */
+ protected void removeLastFieldSeparator(final StringBuffer buffer) {
+ if (StringUtils.endsWith(buffer, fieldSeparator)) {
+ buffer.setLength(buffer.length() - fieldSeparator.length());
+ }
+ }
+
+ /**
+ * Appends to the {@code toString} an {@link Object}
+ * value, printing the full {@code toString} of the
+ * {@link Object} passed in.
+ *
+ * @param buffer the {@link StringBuffer} to populate
+ * @param fieldName the field name
+ * @param value the value 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 Object value, final Boolean fullDetail) {
+ appendFieldStart(buffer, fieldName);
+
+ if (value == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, value, isFullDetail(fullDetail));
+ }
+
+ appendFieldEnd(buffer, fieldName);
+ }
+
+ /**
+ * Appends to the {@code toString} an {@link Object},
+ * correctly interpreting its type.
+ *
+ * <p>This method performs the main lookup by Class type to correctly
+ * route arrays, {@link Collection}s, {@link Map}s and
+ * {@link Objects} to the appropriate method.</p>
+ *
+ * <p>Either detail or summary views can be specified.</p>
+ *
+ * <p>If a cycle is detected, an object will be appended with the
+ * {@code Object.toString()} format.</p>
+ *
+ * @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 detail output detail or not
+ */
+ protected void appendInternal(final StringBuffer buffer, final String fieldName, final Object value, final boolean detail) {
+ if (isRegistered(value)
+ && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
+ appendCyclicObject(buffer, fieldName, value);
+ return;
+ }
+
+ register(value);
+
+ try {
+ if (value instanceof Collection<?>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Collection<?>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Collection<?>) value).size());
+ }
+
+ } else if (value instanceof Map<?, ?>) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Map<?, ?>) value);
+ } else {
+ appendSummarySize(buffer, fieldName, ((Map<?, ?>) value).size());
+ }
+
+ } else if (value instanceof long[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (long[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (long[]) value);
+ }
+
+ } else if (value instanceof int[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (int[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (int[]) value);
+ }
+
+ } else if (value instanceof short[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (short[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (short[]) value);
+ }
+
+ } else if (value instanceof byte[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (byte[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (byte[]) value);
+ }
+
+ } else if (value instanceof char[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (char[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (char[]) value);
+ }
+
+ } else if (value instanceof double[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (double[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (double[]) value);
+ }
+
+ } else if (value instanceof float[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (float[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (float[]) value);
+ }
+
+ } else if (value instanceof boolean[]) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (boolean[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (boolean[]) value);
+ }
+
+ } else if (ObjectUtils.isArray(value)) {
+ if (detail) {
+ appendDetail(buffer, fieldName, (Object[]) value);
+ } else {
+ appendSummary(buffer, fieldName, (Object[]) value);
+ }
+
+ } else if (detail) {
+ appendDetail(buffer, fieldName, value);
+ } else {
+ appendSummary(buffer, fieldName, value);
+ }
+ } finally {
+ unregister(value);
+ }
+ }
+
+ /**
+ * 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} 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} 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 {@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 a summary 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 appendSummary(final StringBuffer buffer, final String fieldName, final Object value) {
+ buffer.append(summaryObjectStartText);
+ buffer.append(getShortClassName(value.getClass()));
+ buffer.append(summaryObjectEndText);
+ }
+
+ /**
+ * <p>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.
+ *
+ * @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 Object[] array) {
+ 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.
+ *
+ * @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 short[] array) {
+ 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}.
+ *
+ * <p>The default indicator is {@code '&lt;null&gt;'}.</p>
+ *
+ * @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.
+ *
+ * <p>The size summary is used to summarize the contents of
+ * {@link Collection}s, {@link Map}s and arrays.</p>
+ *
+ * <p>The output consists of a prefix, the passed in size
+ * and a suffix.</p>
+ *
+ * <p>The default format is {@code '&lt;size=n&gt;'}.</p>
+ *
+ * @param buffer the {@link StringBuffer} to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param size the size to append
+ */
+ protected void appendSummarySize(final StringBuffer buffer, final String fieldName, final int size) {
+ buffer.append(sizeStartText);
+ buffer.append(size);
+ buffer.append(sizeEndText);
+ }
+
+ /**
+ * Is this field to be output in full detail.
+ *
+ * <p>This method converts a detail request into a detail level.
+ * The calling code may request full detail ({@code true}),
+ * but a subclass might ignore that and always return
+ * {@code false}. The calling code may pass in
+ * {@code null} indicating that it doesn't care about
+ * the detail level. In this case the default detail level is
+ * used.</p>
+ *
+ * @param fullDetailRequest the detail level requested
+ * @return whether full detail is to be shown
+ */
+ protected boolean isFullDetail(final Boolean fullDetailRequest) {
+ if (fullDetailRequest == null) {
+ return defaultFullDetail;
+ }
+ return fullDetailRequest.booleanValue();
+ }
+
+ /**
+ * Gets the short class name for a class.
+ *
+ * <p>The short class name is the classname excluding
+ * the package name.</p>
+ *
+ * @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)
+ /**
+ * Gets whether to use the class name.
+ *
+ * @return the current useClassName flag
+ */
+ protected boolean isUseClassName() {
+ return useClassName;
+ }
+
+ /**
+ * Sets whether to use the class name.
+ *
+ * @param useClassName the new useClassName flag
+ */
+ protected void setUseClassName(final boolean useClassName) {
+ this.useClassName = useClassName;
+ }
+
+ /**
+ * Gets whether to output short or long class names.
+ *
+ * @return the current useShortClassName flag
+ * @since 2.0
+ */
+ protected boolean isUseShortClassName() {
+ return useShortClassName;
+ }
+
+ /**
+ * Sets whether to output short or long class names.
+ *
+ * @param useShortClassName the new useShortClassName flag
+ * @since 2.0
+ */
+ protected void setUseShortClassName(final boolean useShortClassName) {
+ this.useShortClassName = useShortClassName;
+ }
+
+ /**
+ * Gets whether to use the identity hash code.
+ *
+ * @return the current useIdentityHashCode flag
+ */
+ protected boolean isUseIdentityHashCode() {
+ return useIdentityHashCode;
+ }
+
+ /**
+ * Sets whether to use the identity hash code.
+ *
+ * @param useIdentityHashCode the new useIdentityHashCode flag
+ */
+ protected void setUseIdentityHashCode(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;
+ }
+
+ /**
+ * Sets whether to output array content detail.
+ *
+ * @param arrayContentDetail the new arrayContentDetail flag
+ */
+ protected void setArrayContentDetail(final boolean arrayContentDetail) {
+ this.arrayContentDetail = arrayContentDetail;
+ }
+
+ /**
+ * Gets the array start text.
+ *
+ * @return the current array start text
+ */
+ protected String getArrayStart() {
+ return arrayStart;
+ }
+
+ /**
+ * Sets the array start text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arrayStart the new array start text
+ */
+ protected void setArrayStart(String arrayStart) {
+ if (arrayStart == null) {
+ arrayStart = 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.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arrayEnd the new array end text
+ */
+ protected void setArrayEnd(String arrayEnd) {
+ if (arrayEnd == null) {
+ arrayEnd = StringUtils.EMPTY;
+ }
+ this.arrayEnd = arrayEnd;
+ }
+
+ /**
+ * Gets the array separator text.
+ *
+ * @return the current array separator text
+ */
+ protected String getArraySeparator() {
+ return arraySeparator;
+ }
+
+ /**
+ * Sets the array separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param arraySeparator the new array separator text
+ */
+ protected void setArraySeparator(String arraySeparator) {
+ if (arraySeparator == null) {
+ arraySeparator = StringUtils.EMPTY;
+ }
+ this.arraySeparator = arraySeparator;
+ }
+
+ /**
+ * Gets the content start text.
+ *
+ * @return the current content start text
+ */
+ protected String getContentStart() {
+ return contentStart;
+ }
+
+ /**
+ * Sets the content start text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param contentStart the new content start text
+ */
+ protected void setContentStart(String contentStart) {
+ if (contentStart == null) {
+ contentStart = StringUtils.EMPTY;
+ }
+ this.contentStart = contentStart;
+ }
+
+ /**
+ * Gets the content end text.
+ *
+ * @return the current content end text
+ */
+ protected String getContentEnd() {
+ return contentEnd;
+ }
+
+ /**
+ * Sets the content end text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param contentEnd the new content end text
+ */
+ protected void setContentEnd(String contentEnd) {
+ if (contentEnd == null) {
+ contentEnd = StringUtils.EMPTY;
+ }
+ this.contentEnd = contentEnd;
+ }
+
+ /**
+ * Gets the field name value separator text.
+ *
+ * @return the current field name value separator text
+ */
+ protected String getFieldNameValueSeparator() {
+ return fieldNameValueSeparator;
+ }
+
+ /**
+ * Sets the field name value separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param fieldNameValueSeparator the new field name value separator text
+ */
+ protected void setFieldNameValueSeparator(String fieldNameValueSeparator) {
+ if (fieldNameValueSeparator == null) {
+ fieldNameValueSeparator = StringUtils.EMPTY;
+ }
+ this.fieldNameValueSeparator = fieldNameValueSeparator;
+ }
+
+ /**
+ * Gets the field separator text.
+ *
+ * @return the current field separator text
+ */
+ protected String getFieldSeparator() {
+ return fieldSeparator;
+ }
+
+ /**
+ * Sets the field separator text.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param fieldSeparator the new field separator text
+ */
+ protected void setFieldSeparator(String fieldSeparator) {
+ if (fieldSeparator == null) {
+ fieldSeparator = StringUtils.EMPTY;
+ }
+ 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.
+ *
+ * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
+ * @since 2.0
+ */
+ protected void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) {
+ this.fieldSeparatorAtEnd = fieldSeparatorAtEnd;
+ }
+
+ /**
+ * Gets the text to output when {@code null} found.
+ *
+ * @return the current text to output when null found
+ */
+ protected String getNullText() {
+ return nullText;
+ }
+
+ /**
+ * Sets the text to output when {@code null} found.
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param nullText the new text to output when null found
+ */
+ protected void setNullText(String nullText) {
+ if (nullText == null) {
+ nullText = StringUtils.EMPTY;
+ }
+ this.nullText = nullText;
+ }
+
+ /**
+ * Gets the start text to output when a {@link Collection},
+ * {@link Map} or array size is output.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @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.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param sizeStartText the new start of size text
+ */
+ protected void setSizeStartText(String sizeStartText) {
+ if (sizeStartText == null) {
+ sizeStartText = StringUtils.EMPTY;
+ }
+ this.sizeStartText = sizeStartText;
+ }
+
+ /**
+ * Gets the end text to output when a {@link Collection},
+ * {@link Map} or array size is output.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @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.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param sizeEndText the new end of size text
+ */
+ protected void setSizeEndText(String sizeEndText) {
+ if (sizeEndText == null) {
+ sizeEndText = StringUtils.EMPTY;
+ }
+ this.sizeEndText = sizeEndText;
+ }
+
+ /**
+ * Gets the start text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * @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.
+ *
+ * <p>This is output before the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectStartText the new start of summary text
+ */
+ protected void setSummaryObjectStartText(String summaryObjectStartText) {
+ if (summaryObjectStartText == null) {
+ summaryObjectStartText = StringUtils.EMPTY;
+ }
+ this.summaryObjectStartText = summaryObjectStartText;
+ }
+
+ /**
+ * Gets the end text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * @return the current end of summary text
+ */
+ protected String getSummaryObjectEndText() {
+ return summaryObjectEndText;
+ }
+
+ /**
+ * Sets the end text to output when an {@link Object} is
+ * output in summary mode.
+ *
+ * <p>This is output after the size value.</p>
+ *
+ * <p>{@code null} is accepted, but will be converted to
+ * an empty String.</p>
+ *
+ * @param summaryObjectEndText the new end of summary text
+ */
+ protected void setSummaryObjectEndText(String summaryObjectEndText) {
+ if (summaryObjectEndText == null) {
+ summaryObjectEndText = StringUtils.EMPTY;
+ }
+ this.summaryObjectEndText = summaryObjectEndText;
+ }
+
+ /**
+ * Default {@link ToStringStyle}.
+ *
+ * <p>This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.</p>
+ */
+ private static final class DefaultToStringStyle extends ToStringStyle {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ DefaultToStringStyle() {
+ }
+
+ /**
+ * Ensure Singleton after serialization.
+ *
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return DEFAULT_STYLE;
+ }
+
+ }
+
+ /**
+ * {@link ToStringStyle} that does not print out
+ * the field names.
+ *
+ * <p>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.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ 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 hashcode.
+ *
+ * <p>This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.</p>
+ */
+ private static final class ShortPrefixToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ ShortPrefixToStringStyle() {
+ this.setUseShortClassName(true);
+ this.setUseIdentityHashCode(false);
+ }
+
+ /**
+ * Ensure <code>Singleton</ode> after serialization.
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return SHORT_PREFIX_STYLE;
+ }
+
+ }
+
+ /**
+ * {@link ToStringStyle} that does not print out the
+ * classname, identity hashcode, content start or field name.
+ *
+ * <p>This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.</p>
+ */
+ private static final class SimpleToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ SimpleToStringStyle() {
+ this.setUseClassName(false);
+ this.setUseIdentityHashCode(false);
+ this.setUseFieldNames(false);
+ this.setContentStart(StringUtils.EMPTY);
+ this.setContentEnd(StringUtils.EMPTY);
+ }
+
+ /**
+ * Ensure <code>Singleton</ode> after serialization.
+ * @return the singleton
+ */
+ private Object readResolve() {
+ return SIMPLE_STYLE;
+ }
+
+ }
+
+ /**
+ * {@link ToStringStyle} that outputs on multiple lines.
+ *
+ * <p>This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.</p>
+ */
+ private static final class MultiLineToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ 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 classname
+ * and identity hash code but prints content start and field names.
+ *
+ * <p>This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.</p>
+ */
+ private static final class NoClassNameToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * <p>Use the static constant rather than instantiating.</p>
+ */
+ 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.
+ *
+ * <p>
+ * This is an inner class rather than using
+ * {@link StandardToStringStyle} to ensure its immutability.
+ * </p>
+ *
+ * @since 3.4
+ * @see <a href="https://www.json.org/">json.org</a>
+ */
+ private static final class JsonToStringStyle extends ToStringStyle {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String FIELD_NAME_QUOTE = "\"";
+
+ /**
+ * Constructor.
+ *
+ * <p>
+ * Use the static constant rather than instantiating.
+ * </p>
+ */
+ 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("\"<size=");
+ this.setSizeEndText(">\"");
+ }
+
+ @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;
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java b/src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java
new file mode 100644
index 000000000..c6dad14f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/ToStringSummary.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.builder;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation on the fields to get the summary instead of the detailed
+ * information when using {@link ReflectionToStringBuilder}.
+ *
+ * <p>
+ * Notice that not all {@link ToStringStyle} implementations support the
+ * appendSummary method.
+ * </p>
+ *
+ * @since 3.8
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ToStringSummary {
+ // empty
+}
diff --git a/src/main/java/org/apache/commons/lang3/builder/package-info.java b/src/main/java/org/apache/commons/lang3/builder/package-info.java
new file mode 100644
index 000000000..216706718
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/builder/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Assists in creating consistent {@code equals(Object)}, {@code toString()}, {@code hashCode()}, and {@code compareTo(Object)} methods.
+ * These classes are not thread-safe.
+ *
+ * <p>When you write a {@link java.lang.Object#hashCode() hashCode()}, do you check Bloch's Effective Java? No?
+ * You just hack in a quick number?
+ * Well {@link org.apache.commons.lang3.builder.HashCodeBuilder} will save your day.
+ * It, and its buddies ({@link org.apache.commons.lang3.builder.EqualsBuilder}, {@link org.apache.commons.lang3.builder.CompareToBuilder}, {@link org.apache.commons.lang3.builder.ToStringBuilder}), take care of the nasty bits while you focus on the important bits, like which fields will go into making up the hashcode.</p>
+ *
+ * @see Object#equals(Object)
+ * @see Object#toString()
+ * @see Object#hashCode()
+ * @see Comparable#compareTo(Object)
+ *
+ * @since 1.0
+ */
+package org.apache.commons.lang3.builder;
diff --git a/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java b/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java
new file mode 100644
index 000000000..9c7b8260b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/compare/ComparableUtils.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.compare;
+
+import java.util.function.Predicate;
+
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Utility library to provide helper methods for translating {@link Comparable#compareTo} result into a boolean.
+ *
+ * <p>Example: {@code boolean x = is(myComparable).lessThanOrEqualTo(otherComparable)}</p>
+ *
+ * <p>#ThreadSafe#</p>
+ *
+ * @since 3.10
+ */
+public class ComparableUtils {
+
+ /**
+ * Provides access to the available methods
+ *
+ * @param <A> the type of objects that this object may be compared against.
+ */
+ public static class ComparableCheckBuilder<A extends Comparable<A>> {
+
+ private final A a;
+
+ private ComparableCheckBuilder(final A a) {
+ this.a = a;
+ }
+
+ /**
+ * Checks if {@code [b <= a <= c]} or {@code [b >= a >= c]} where the {@code a} is object passed to {@link #is}.
+ *
+ * @param b the object to compare to the base object
+ * @param c the object to compare to the base object
+ * @return true if the base object is between b and c
+ */
+ public boolean between(final A b, final A c) {
+ return betweenOrdered(b, c) || betweenOrdered(c, b);
+ }
+
+ /**
+ * Checks if {@code (b < a < c)} or {@code (b > a > c)} where the {@code a} is object passed to {@link #is}.
+ *
+ * @param b the object to compare to the base object
+ * @param c the object to compare to the base object
+ * @return true if the base object is between b and c and not equal to those
+ */
+ public boolean betweenExclusive(final A b, final A c) {
+ return betweenOrderedExclusive(b, c) || betweenOrderedExclusive(c, b);
+ }
+
+ private boolean betweenOrdered(final A b, final A c) {
+ return greaterThanOrEqualTo(b) && lessThanOrEqualTo(c);
+ }
+
+ private boolean betweenOrderedExclusive(final A b, final A c) {
+ return greaterThan(b) && lessThan(c);
+ }
+
+ /**
+ * Checks if the object passed to {@link #is} is equal to {@code b}
+ *
+ * @param b the object to compare to the base object
+ * @return true if the value returned by {@link Comparable#compareTo} is equal to {@code 0}
+ */
+ public boolean equalTo(final A b) {
+ return a.compareTo(b) == 0;
+ }
+
+ /**
+ * Checks if the object passed to {@link #is} is greater than {@code b}
+ *
+ * @param b the object to compare to the base object
+ * @return true if the value returned by {@link Comparable#compareTo} is greater than {@code 0}
+ */
+ public boolean greaterThan(final A b) {
+ return a.compareTo(b) > 0;
+ }
+
+ /**
+ * Checks if the object passed to {@link #is} is greater than or equal to {@code b}
+ *
+ * @param b the object to compare to the base object
+ * @return true if the value returned by {@link Comparable#compareTo} is greater than or equal to {@code 0}
+ */
+ public boolean greaterThanOrEqualTo(final A b) {
+ return a.compareTo(b) >= 0;
+ }
+
+ /**
+ * Checks if the object passed to {@link #is} is less than {@code b}
+ *
+ * @param b the object to compare to the base object
+ * @return true if the value returned by {@link Comparable#compareTo} is less than {@code 0}
+ */
+ public boolean lessThan(final A b) {
+ return a.compareTo(b) < 0;
+ }
+
+ /**
+ * Checks if the object passed to {@link #is} is less than or equal to {@code b}
+ *
+ * @param b the object to compare to the base object
+ * @return true if the value returned by {@link Comparable#compareTo} is less than or equal to {@code 0}
+ */
+ public boolean lessThanOrEqualTo(final A b) {
+ return a.compareTo(b) <= 0;
+ }
+ }
+
+ /**
+ * Checks if {@code [b <= a <= c]} or {@code [b >= a >= c]} where the {@code a} is the tested object.
+ *
+ * @param b the object to compare to the tested object
+ * @param c the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the tested object is between b and c
+ */
+ public static <A extends Comparable<A>> Predicate<A> between(final A b, final A c) {
+ return a -> is(a).between(b, c);
+ }
+
+ /**
+ * Checks if {@code (b < a < c)} or {@code (b > a > c)} where the {@code a} is the tested object.
+ *
+ * @param b the object to compare to the tested object
+ * @param c the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the tested object is between b and c and not equal to those
+ */
+ public static <A extends Comparable<A>> Predicate<A> betweenExclusive(final A b, final A c) {
+ return a -> is(a).betweenExclusive(b, c);
+ }
+
+ /**
+ * Checks if the tested object is greater than or equal to {@code b}
+ *
+ * @param b the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the value returned by {@link Comparable#compareTo}
+ * is greater than or equal to {@code 0}
+ */
+ public static <A extends Comparable<A>> Predicate<A> ge(final A b) {
+ return a -> is(a).greaterThanOrEqualTo(b);
+ }
+
+ /**
+ * Checks if the tested object is greater than {@code b}
+ *
+ * @param b the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the value returned by {@link Comparable#compareTo} is greater than {@code 0}
+ */
+ public static <A extends Comparable<A>> Predicate<A> gt(final A b) {
+ return a -> is(a).greaterThan(b);
+ }
+
+ /**
+ * Provides access to the available methods
+ *
+ * @param a base object in the further comparison
+ * @param <A> type of the base object
+ * @return a builder object with further methods
+ */
+ public static <A extends Comparable<A>> ComparableCheckBuilder<A> is(final A a) {
+ return new ComparableCheckBuilder<>(a);
+ }
+
+ /**
+ * Checks if the tested object is less than or equal to {@code b}
+ *
+ * @param b the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the value returned by {@link Comparable#compareTo}
+ * is less than or equal to {@code 0}
+ */
+ public static <A extends Comparable<A>> Predicate<A> le(final A b) {
+ return a -> is(a).lessThanOrEqualTo(b);
+ }
+
+ /**
+ * Checks if the tested object is less than {@code b}
+ *
+ * @param b the object to compare to the tested object
+ * @param <A> type of the test object
+ * @return a predicate for true if the value returned by {@link Comparable#compareTo} is less than {@code 0}
+ */
+ public static <A extends Comparable<A>> Predicate<A> lt(final A b) {
+ return a -> is(a).lessThan(b);
+ }
+
+ /**
+ * Returns the greater of two {@link Comparable} values, ignoring null.
+ * <p>
+ * For three or more values, use {@link ObjectUtils#max(Comparable...)}.
+ * </p>
+ *
+ * @param <A> Type of what we are comparing.
+ * @param comparable1 an argument.
+ * @param comparable2 another argument.
+ * @return the largest of {@code c1} and {@code c2}.
+ * @see ObjectUtils#max(Comparable...)
+ * @since 3.13.0
+ */
+ public static <A extends Comparable<A>> A max(final A comparable1, final A comparable2) {
+ return ObjectUtils.compare(comparable1, comparable2, false) > 0 ? comparable1 : comparable2;
+ }
+
+ /**
+ * Returns the lesser of two {@link Comparable} values, ignoring null.
+ * <p>
+ * For three or more values, use {@link ObjectUtils#min(Comparable...)}.
+ * </p>
+ *
+ * @param <A> Type of what we are comparing.
+ * @param comparable1 an argument.
+ * @param comparable2 another argument.
+ * @return the largest of {@code c1} and {@code c2}.
+ * @see ObjectUtils#min(Comparable...)
+ * @since 3.13.0
+ */
+ public static <A extends Comparable<A>> A min(final A comparable1, final A comparable2) {
+ return ObjectUtils.compare(comparable1, comparable2, true) < 0 ? comparable1 : comparable2;
+ }
+
+ private ComparableUtils() {}
+}
diff --git a/src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java b/src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java
new file mode 100644
index 000000000..2bb5840d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/compare/ObjectToStringComparator.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.compare;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Compares Object's {@link Object#toString()} values.
+ *
+ * This class is stateless.
+ *
+ * @since 3.10
+ */
+public final class ObjectToStringComparator implements Comparator<Object>, Serializable {
+
+ /**
+ * Singleton instance.
+ *
+ * This class is stateless.
+ */
+ public static final ObjectToStringComparator INSTANCE = new ObjectToStringComparator();
+
+ /**
+ * For {@link Serializable}.
+ */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public int compare(final Object o1, final Object o2) {
+ if (o1 == null && o2 == null) {
+ return 0;
+ }
+ if (o1 == null) {
+ return 1;
+ }
+ if (o2 == null) {
+ return -1;
+ }
+ final String string1 = o1.toString();
+ final String string2 = o2.toString();
+ // No guarantee that toString() returns a non-null value, despite what Spotbugs thinks.
+ if (string1 == null && string2 == null) {
+ return 0;
+ }
+ if (string1 == null) {
+ return 1;
+ }
+ if (string2 == null) {
+ return -1;
+ }
+ return string1.compareTo(string2);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/compare/package-info.java b/src/main/java/org/apache/commons/lang3/compare/package-info.java
new file mode 100644
index 000000000..99136ede8
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/compare/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides classes to work with the {@link java.lang.Comparable} and {@link java.util.Comparator} interfaces.
+ *
+ * @since 3.10
+ */
+package org.apache.commons.lang3.compare;
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java
new file mode 100644
index 000000000..27c530245
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/AbstractCircuitBreaker.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for circuit breakers.
+ *
+ * @param <T> the type of the value monitored by this circuit breaker
+ * @since 3.5
+ */
+public abstract class AbstractCircuitBreaker<T> implements CircuitBreaker<T> {
+
+ /**
+ * The name of the <em>open</em> 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> 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 <em>open</em> 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
+ * <strong>null</strong>, 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
+ * transitions. This is done to avoid complex if-conditions in the code of
+ * {@link CircuitBreaker}.
+ */
+ protected enum State {
+
+ /** The closed state. */
+ CLOSED {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public State oppositeState() {
+ return OPEN;
+ }
+ },
+
+ /** The open state. */
+ OPEN {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public State oppositeState() {
+ return CLOSED;
+ }
+ };
+
+ /**
+ * Returns the opposite state to the represented state. This is useful
+ * for flipping the current state.
+ *
+ * @return the opposite state
+ */
+ public abstract State oppositeState();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AbstractFutureProxy.java b/src/main/java/org/apache/commons/lang3/concurrent/AbstractFutureProxy.java
new file mode 100644
index 000000000..45ae4bd74
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/AbstractFutureProxy.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Proxies to a {@link Future} for subclassing.
+ *
+ * @param <V> The result type returned by this Future's {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ * @since 3.13.0
+ */
+public abstract class AbstractFutureProxy<V> implements Future<V> {
+
+ private final Future<V> future;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param future the delegate.
+ */
+ public AbstractFutureProxy(final Future<V> future) {
+ this.future = Objects.requireNonNull(future, "future");
+ }
+
+ @Override
+ public boolean cancel(final boolean mayInterruptIfRunning) {
+ return future.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public V get() throws InterruptedException, ExecutionException {
+ return future.get();
+ }
+
+ @Override
+ public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return future.get(timeout, unit);
+ }
+
+ /**
+ * Gets the delegate.
+ *
+ * @return the delegate.
+ */
+ public Future<V> getFuture() {
+ return future;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return future.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return future.isDone();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java
new file mode 100644
index 000000000..4c2982d7d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A specialized implementation of the {@link ConcurrentInitializer} interface
+ * based on an {@link AtomicReference} variable.
+ *
+ * <p>
+ * This class maintains a member field of type {@link AtomicReference}. It
+ * implements the following algorithm to create and initialize an object in its
+ * {@link #get()} method:
+ * </p>
+ * <ul>
+ * <li>First it is checked whether the {@link AtomicReference} variable contains
+ * already a value. If this is the case, the value is directly returned.</li>
+ * <li>Otherwise the {@link #initialize()} method is called. This method must be
+ * defined in concrete subclasses to actually create the managed object.</li>
+ * <li>After the object was created by {@link #initialize()} it is checked
+ * whether the {@link AtomicReference} variable is still undefined. This has to
+ * be done because in the meantime another thread may have initialized the
+ * object. If the reference is still empty, the newly created object is stored
+ * in it and returned by this method.</li>
+ * <li>Otherwise the value stored in the {@link AtomicReference} is returned.</li>
+ * </ul>
+ * <p>
+ * Because atomic variables are used this class does not need any
+ * synchronization. So there is no danger of deadlock, and access to the managed
+ * object is efficient. However, if multiple threads access the {@code
+ * AtomicInitializer} object before it has been initialized almost at the same
+ * time, it can happen that {@link #initialize()} is called multiple times. The
+ * algorithm outlined above guarantees that {@link #get()} always returns the
+ * same object though.
+ * </p>
+ * <p>
+ * Compared with the {@link LazyInitializer} class, this class can be more
+ * efficient because it does not need synchronization. The drawback is that the
+ * {@link #initialize()} method can be called multiple times which may be
+ * problematic if the creation of the managed object is expensive. As a rule of
+ * thumb this initializer implementation is preferable if there are not too many
+ * threads involved and the probability that multiple threads access an
+ * uninitialized object is small. If there is high parallelism,
+ * {@link LazyInitializer} is more appropriate.
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public abstract class AtomicInitializer<T> implements ConcurrentInitializer<T> {
+ /** Holds the reference to the managed object. */
+ private final AtomicReference<T> reference = new AtomicReference<>();
+
+ /**
+ * Returns the object managed by this initializer. The object is created if
+ * it is not available yet and stored internally. This method always returns
+ * the same object.
+ *
+ * @return the object created by this {@link AtomicInitializer}
+ * @throws ConcurrentException if an error occurred during initialization of
+ * the object
+ */
+ @Override
+ public T get() throws ConcurrentException {
+ T result = reference.get();
+
+ if (result == null) {
+ result = initialize();
+ if (!reference.compareAndSet(null, result)) {
+ // another thread has initialized the reference
+ result = reference.get();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates and initializes the object managed by this {@code
+ * AtomicInitializer}. This method is called by {@link #get()} when the
+ * managed object is not available yet. An implementation can focus on the
+ * creation of the object. No synchronization is needed, as this is already
+ * handled by {@code get()}. As stated by the class comment, it is possible
+ * that this method is called multiple times.
+ *
+ * @return the managed data object
+ * @throws ConcurrentException if an error occurs during object creation
+ */
+ protected abstract T initialize() throws ConcurrentException;
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java
new file mode 100644
index 000000000..3616a86da
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A specialized {@link ConcurrentInitializer} implementation which is similar
+ * to {@link AtomicInitializer}, but ensures that the {@link #initialize()}
+ * method is called only once.
+ *
+ * <p>
+ * As {@link AtomicInitializer} this class is based on atomic variables, so it
+ * can create an object under concurrent access without synchronization.
+ * However, it implements an additional check to guarantee that the
+ * {@link #initialize()} method which actually creates the object cannot be
+ * called multiple times.
+ * </p>
+ * <p>
+ * Because of this additional check this implementation is slightly less
+ * efficient than {@link AtomicInitializer}, but if the object creation in the
+ * {@code initialize()} method is expensive or if multiple invocations of
+ * {@code initialize()} are problematic, it is the better alternative.
+ * </p>
+ * <p>
+ * From its semantics this class has the same properties as
+ * {@link LazyInitializer}. It is a &quot;save&quot; implementation of the lazy
+ * initializer pattern. Comparing both classes in terms of efficiency is
+ * difficult because which one is faster depends on multiple factors. Because
+ * {@link AtomicSafeInitializer} does not use synchronization at all it probably
+ * outruns {@link LazyInitializer}, at least under low or moderate concurrent
+ * access. Developers should run their own benchmarks on the expected target
+ * platform to decide which implementation is suitable for their specific use
+ * case.
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public abstract class AtomicSafeInitializer<T> implements
+ ConcurrentInitializer<T> {
+ /** A guard which ensures that initialize() is called only once. */
+ private final AtomicReference<AtomicSafeInitializer<T>> factory =
+ new AtomicReference<>();
+
+ /** Holds the reference to the managed object. */
+ private final AtomicReference<T> reference = new AtomicReference<>();
+
+ /**
+ * Gets (and initialize, if not initialized yet) the required object
+ *
+ * @return lazily initialized object
+ * @throws ConcurrentException if the initialization of the object causes an
+ * exception
+ */
+ @Override
+ public final T get() throws ConcurrentException {
+ T result;
+
+ while ((result = reference.get()) == null) {
+ if (factory.compareAndSet(null, this)) {
+ reference.set(initialize());
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates and initializes the object managed by this
+ * {@link AtomicInitializer}. This method is called by {@link #get()} when
+ * the managed object is not available yet. An implementation can focus on
+ * the creation of the object. No synchronization is needed, as this is
+ * already handled by {@code get()}. This method is guaranteed to be called
+ * only once.
+ *
+ * @return the managed data object
+ * @throws ConcurrentException if an error occurs during object creation
+ */
+ protected abstract T initialize() throws ConcurrentException;
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
new file mode 100644
index 000000000..c74e14afa
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * A class that allows complex initialization operations in a background task.
+ *
+ * <p>
+ * Applications often have to do some expensive initialization steps when they
+ * are started, e.g. constructing a connection to a database, reading a
+ * configuration file, etc. Doing these things in parallel can enhance
+ * performance as the CPU load can be improved. However, when access to the
+ * resources initialized in a background thread is actually required,
+ * synchronization has to be performed to ensure that their initialization is
+ * complete.
+ * </p>
+ * <p>
+ * This abstract base class provides support for this use case. A concrete
+ * subclass must implement the {@link #initialize()} method. Here an arbitrary
+ * initialization can be implemented, and a result object can be returned. With
+ * this method in place the basic usage of this class is as follows (where
+ * {@code MyBackgroundInitializer} is a concrete subclass):
+ * </p>
+ *
+ * <pre>
+ * MyBackgroundInitializer initializer = new MyBackgroundInitializer();
+ * initializer.start();
+ * // Now do some other things. Initialization runs in a parallel thread
+ * ...
+ * // Wait for the end of initialization and access the result object
+ * Object result = initializer.get();
+ * </pre>
+ *
+ * <p>
+ * After the construction of a {@link BackgroundInitializer} object its
+ * {@link #start()} method has to be called. This starts the background
+ * processing. The application can now continue to do other things. When it
+ * needs access to the object produced by the {@link BackgroundInitializer} it
+ * calls its {@link #get()} method. If initialization is already complete,
+ * {@link #get()} returns the result object immediately. Otherwise it blocks
+ * until the result object is fully constructed.
+ * </p>
+ * <p>
+ * {@link BackgroundInitializer} is a thin wrapper around a {@link Future}
+ * object and uses an {@link ExecutorService} for running the background
+ * initialization task. It is possible to pass in an {@link ExecutorService} at
+ * construction time or set one using {@code setExternalExecutor()} before
+ * {@code start()} was called. Then this object is used to spawn the background
+ * task. If no {@link ExecutorService} has been provided, {@code
+ * BackgroundInitializer} creates a temporary {@link ExecutorService} and
+ * destroys it when initialization is complete.
+ * </p>
+ * <p>
+ * The methods provided by {@link BackgroundInitializer} provide for minimal
+ * interaction with the wrapped {@link Future} object. It is also possible to
+ * obtain the {@link Future} object directly. Then the enhanced functionality
+ * offered by {@link Future} can be used, e.g. to check whether the background
+ * operation is complete or to cancel the operation.
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public abstract class BackgroundInitializer<T> implements
+ ConcurrentInitializer<T> {
+ /** The external executor service for executing tasks. */
+ private ExecutorService externalExecutor; // @GuardedBy("this")
+
+ /** A reference to the executor service that is actually used. */
+ private ExecutorService executor; // @GuardedBy("this")
+
+ /** Stores the handle to the background task. */
+ private Future<T> future; // @GuardedBy("this")
+
+ /**
+ * Creates a new instance of {@link BackgroundInitializer}. No external
+ * {@link ExecutorService} is used.
+ */
+ protected BackgroundInitializer() {
+ this(null);
+ }
+
+ /**
+ * Creates a new instance of {@link BackgroundInitializer} and initializes
+ * it with the given {@link ExecutorService}. If the {@link ExecutorService}
+ * is not null, the background task for initializing this object will be
+ * scheduled at this service. Otherwise a new temporary {@code
+ * ExecutorService} is created.
+ *
+ * @param exec an external {@link ExecutorService} to be used for task
+ * execution
+ */
+ protected BackgroundInitializer(final ExecutorService exec) {
+ setExternalExecutor(exec);
+ }
+
+ /**
+ * Returns the external {@link ExecutorService} to be used by this class.
+ *
+ * @return the {@link ExecutorService}
+ */
+ public final synchronized ExecutorService getExternalExecutor() {
+ return externalExecutor;
+ }
+
+ /**
+ * Returns a flag whether this {@link BackgroundInitializer} has already
+ * been started.
+ *
+ * @return a flag whether the {@link #start()} method has already been
+ * called
+ */
+ public synchronized boolean isStarted() {
+ return future != null;
+ }
+
+ /**
+ * Sets an {@link ExecutorService} to be used by this class. The {@code
+ * ExecutorService} passed to this method is used for executing the
+ * background task. Thus it is possible to re-use an already existing
+ * {@link ExecutorService} or to use a specially configured one. If no
+ * {@link ExecutorService} is set, this instance creates a temporary one and
+ * destroys it after background initialization is complete. Note that this
+ * method must be called before {@link #start()}; otherwise an exception is
+ * thrown.
+ *
+ * @param externalExecutor the {@link ExecutorService} to be used
+ * @throws IllegalStateException if this initializer has already been
+ * started
+ */
+ public final synchronized void setExternalExecutor(
+ final ExecutorService externalExecutor) {
+ if (isStarted()) {
+ throw new IllegalStateException(
+ "Cannot set ExecutorService after start()!");
+ }
+
+ this.externalExecutor = externalExecutor;
+ }
+
+ /**
+ * Starts the background initialization. With this method the initializer
+ * becomes active and invokes the {@link #initialize()} method in a
+ * background task. A {@link BackgroundInitializer} can be started exactly
+ * once. The return value of this method determines whether the start was
+ * successful: only the first invocation of this method returns <b>true</b>,
+ * following invocations will return <b>false</b>.
+ *
+ * @return a flag whether the initializer could be started successfully
+ */
+ public synchronized boolean start() {
+ // Not yet started?
+ if (!isStarted()) {
+
+ // Determine the executor to use and whether a temporary one has to
+ // be created
+ final ExecutorService tempExec;
+ executor = getExternalExecutor();
+ if (executor == null) {
+ executor = tempExec = createExecutor();
+ } else {
+ tempExec = null;
+ }
+
+ future = executor.submit(createTask(tempExec));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 Future} object that was created when {@link #start()}
+ * was called. Therefore this method can only be called after {@code
+ * start()}.
+ *
+ * @return the {@link Future} object wrapped by this initializer
+ * @throws IllegalStateException if {@link #start()} has not been called
+ */
+ public synchronized Future<T> 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 <b>null</b>). 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;
+ }
+
+ /**
+ * Performs the initialization. This method is called in a background task
+ * when this {@link BackgroundInitializer} is started. It must be
+ * implemented by a concrete subclass. An implementation is free to perform
+ * arbitrary initialization. The object returned by this method can be
+ * queried using the {@link #get()} method.
+ *
+ * @return a result object
+ * @throws Exception if an error occurs
+ */
+ protected abstract T initialize() throws Exception;
+
+ /**
+ * 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<T> 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<T> {
+ /** 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();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java b/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java
new file mode 100644
index 000000000..20a6ed80a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/BasicThreadFactory.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * An implementation of the {@link ThreadFactory} interface that provides some
+ * configuration options for the threads it creates.
+ *
+ * <p>
+ * A {@link ThreadFactory} is used for instance by an {@link ExecutorService} to
+ * create the threads it uses for executing tasks. In many cases users do not
+ * have to care about a {@link ThreadFactory} because the default one used by an
+ * {@link ExecutorService} will do. However, if there are special requirements
+ * for the threads, a custom {@link ThreadFactory} has to be created.
+ * </p>
+ * <p>
+ * This class provides some frequently needed configuration options for the
+ * threads it creates. These are the following:
+ * </p>
+ * <ul>
+ * <li>A name pattern for the threads created by this factory can be specified.
+ * This is often useful if an application uses multiple executor services for
+ * different purposes. If the names of the threads used by these services have
+ * meaningful names, log output or exception traces can be much easier to read.
+ * Naming patterns are <em>format strings</em> as used by the {@code
+ * String.format()} method. The string can contain the place holder {@code %d}
+ * which will be replaced by the number of the current thread ({@code
+ * ThreadFactoryImpl} keeps a counter of the threads it has already created).
+ * For instance, the naming pattern {@code "My %d. worker thread"} will result
+ * in thread names like {@code "My 1. worker thread"}, {@code
+ * "My 2. worker thread"} and so on.</li>
+ * <li>A flag whether the threads created by this factory should be daemon
+ * threads. This can impact the exit behavior of the current Java application
+ * because the JVM shuts down if there are only daemon threads running.</li>
+ * <li>The priority of the thread. Here an integer value can be provided. The
+ * {@code java.lang.Thread} class defines constants for valid ranges of priority
+ * values.</li>
+ * <li>The {@link UncaughtExceptionHandler} for the thread. This handler is
+ * called if an uncaught exception occurs within the thread.</li>
+ * </ul>
+ * <p>
+ * {@link BasicThreadFactory} wraps another thread factory which actually
+ * creates new threads. The configuration options are set on the threads created
+ * by the wrapped thread factory. On construction time the factory to be wrapped
+ * can be specified. If none is provided, a default {@link ThreadFactory} is
+ * used.
+ * </p>
+ * <p>
+ * Instances of {@link BasicThreadFactory} are not created directly, but the
+ * nested {@link Builder} class is used for this purpose. Using the builder only
+ * the configuration options an application is interested in need to be set. The
+ * following example shows how a {@link BasicThreadFactory} is created and
+ * installed in an {@link ExecutorService}:
+ * </p>
+ *
+ * <pre>
+ * // Create a factory that produces daemon threads with a naming pattern and
+ * // a priority
+ * BasicThreadFactory factory = new BasicThreadFactory.Builder()
+ * .namingPattern(&quot;workerthread-%d&quot;)
+ * .daemon(true)
+ * .priority(Thread.MAX_PRIORITY)
+ * .build();
+ * // Create an executor service for single-threaded execution
+ * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
+ * </pre>
+ *
+ * @since 3.0
+ */
+public class BasicThreadFactory implements ThreadFactory {
+ /** A counter for the threads created by this factory. */
+ private final AtomicLong threadCounter;
+
+ /** Stores the wrapped factory. */
+ private final ThreadFactory wrappedFactory;
+
+ /** Stores the uncaught exception handler. */
+ private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
+
+ /** Stores the naming pattern for newly created threads. */
+ private final String namingPattern;
+
+ /** Stores the priority. */
+ private final Integer priority;
+
+ /** Stores the daemon status flag. */
+ private final Boolean daemon;
+
+ /**
+ * Creates a new instance of {@link ThreadFactory} and configures it
+ * from the specified {@link Builder} object.
+ *
+ * @param builder the {@link Builder} object
+ */
+ private BasicThreadFactory(final Builder builder) {
+ if (builder.wrappedFactory == null) {
+ wrappedFactory = Executors.defaultThreadFactory();
+ } else {
+ wrappedFactory = builder.wrappedFactory;
+ }
+
+ namingPattern = builder.namingPattern;
+ priority = builder.priority;
+ daemon = builder.daemon;
+ uncaughtExceptionHandler = builder.exceptionHandler;
+
+ threadCounter = new AtomicLong();
+ }
+
+ /**
+ * Returns the wrapped {@link ThreadFactory}. This factory is used for
+ * actually creating threads. This method never returns <b>null</b>. 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;
+ }
+
+ /**
+ * Returns the naming pattern for naming newly created threads. Result can
+ * be <b>null</b> if no naming pattern was provided.
+ *
+ * @return the naming pattern
+ */
+ public final String getNamingPattern() {
+ return namingPattern;
+ }
+
+ /**
+ * Returns the daemon flag. This flag determines whether newly created
+ * threads should be daemon threads. If <b>true</b>, this factory object
+ * calls {@code setDaemon(true)} on the newly created threads. Result can be
+ * <b>null</b> 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 <b>null</b> if no priority was specified.
+ *
+ * @return the priority for newly created threads
+ */
+ public final Integer getPriority() {
+ return priority;
+ }
+
+ /**
+ * Returns the {@link UncaughtExceptionHandler} for the threads created by
+ * this factory. Result can be <b>null</b> 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
+ * {@link #newThread(Runnable)} method is invoked.
+ *
+ * @return the number of threads created by this factory
+ */
+ public long getThreadCount() {
+ return threadCounter.get();
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public Thread newThread(final Runnable runnable) {
+ final Thread thread = getWrappedFactory().newThread(runnable);
+ initializeThread(thread);
+
+ return thread;
+ }
+
+ /**
+ * Initializes the specified thread. This method is called by
+ * {@link #newThread(Runnable)} after a new thread has been obtained from
+ * the wrapped thread factory. It initializes the thread according to the
+ * options set for this factory.
+ *
+ * @param thread the thread to be initialized
+ */
+ private void initializeThread(final Thread thread) {
+
+ if (getNamingPattern() != null) {
+ final Long count = Long.valueOf(threadCounter.incrementAndGet());
+ thread.setName(String.format(getNamingPattern(), count));
+ }
+
+ if (getUncaughtExceptionHandler() != null) {
+ thread.setUncaughtExceptionHandler(getUncaughtExceptionHandler());
+ }
+
+ if (getPriority() != null) {
+ thread.setPriority(getPriority().intValue());
+ }
+
+ if (getDaemonFlag() != null) {
+ thread.setDaemon(getDaemonFlag().booleanValue());
+ }
+ }
+
+ /**
+ * A <em>builder</em> class for creating instances of {@code
+ * BasicThreadFactory}.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ */
+ public static class Builder
+ implements org.apache.commons.lang3.builder.Builder<BasicThreadFactory> {
+
+ /** 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
+ * <b>null</b>)
+ * @return a reference to this {@link Builder}
+ * @throws NullPointerException if the passed in {@link ThreadFactory}
+ * is <b>null</b>
+ */
+ 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 <b>null</b>)
+ * @return a reference to this {@link Builder}
+ * @throws NullPointerException if the naming pattern is <b>null</b>
+ */
+ 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 <b>true</b> 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
+ * <b>null</b>)
+ * @return a reference to this {@link Builder}
+ * @throws NullPointerException if the exception handler is <b>null</b>
+ */
+ 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;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java
new file mode 100644
index 000000000..cccb43d92
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/CallableBackgroundInitializer.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * A specialized {@link BackgroundInitializer} implementation that wraps a
+ * {@link Callable} object.
+ *
+ * <p>
+ * An instance of this class is initialized with a {@link Callable} object when
+ * it is constructed. The implementation of the {@link #initialize()} method
+ * defined in the super class delegates to this {@link Callable} so that the
+ * {@link Callable} is executed in the background thread.
+ * </p>
+ * <p>
+ * The {@code java.util.concurrent.Callable} interface is a standard mechanism
+ * of the JDK to define tasks to be executed by another thread. The {@code
+ * CallableBackgroundInitializer} class allows combining this standard interface
+ * with the background initializer API.
+ * </p>
+ * <p>
+ * Usage of this class is very similar to the default usage pattern of the
+ * {@link BackgroundInitializer} class: Just create an instance and provide the
+ * {@link Callable} object to be executed, then call the initializer's
+ * {@link #start()} method. This causes the {@link Callable} to be executed in
+ * another thread. When the results of the {@link Callable} are needed the
+ * initializer's {@link #get()} method can be called (which may block until
+ * background execution is complete). The following code fragment shows a
+ * typical usage example:
+ * </p>
+ *
+ * <pre>
+ * // a Callable that performs a complex computation
+ * Callable&lt;Integer&gt; computationCallable = new MyComputationCallable();
+ * // setup the background initializer
+ * CallableBackgroundInitializer&lt;Integer&gt; initializer =
+ * new CallableBackgroundInitializer(computationCallable);
+ * initializer.start();
+ * // Now do some other things. Initialization runs in a parallel thread
+ * ...
+ * // Wait for the end of initialization and access the result
+ * Integer result = initializer.get();
+ * </pre>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public class CallableBackgroundInitializer<T> extends BackgroundInitializer<T> {
+ /** The Callable to be executed. */
+ private final Callable<T> callable;
+
+ /**
+ * Creates a new instance of {@link CallableBackgroundInitializer} and sets
+ * the {@link Callable} to be executed in a background thread.
+ *
+ * @param call the {@link Callable} (must not be <b>null</b>)
+ * @throws IllegalArgumentException if the {@link Callable} is <b>null</b>
+ */
+ public CallableBackgroundInitializer(final Callable<T> call) {
+ checkCallable(call);
+ callable = call;
+ }
+
+ /**
+ * Creates a new instance of {@link CallableBackgroundInitializer} and
+ * initializes it with the {@link Callable} to be executed in a background
+ * thread and the {@link ExecutorService} for managing the background
+ * execution.
+ *
+ * @param call the {@link Callable} (must not be <b>null</b>)
+ * @param exec an external {@link ExecutorService} to be used for task
+ * execution
+ * @throws IllegalArgumentException if the {@link Callable} is <b>null</b>
+ */
+ public CallableBackgroundInitializer(final Callable<T> call, final ExecutorService exec) {
+ super(exec);
+ checkCallable(call);
+ 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.
+ *
+ * @param callable the object to check
+ * @throws IllegalArgumentException if the {@link Callable} is <b>null</b>
+ */
+ private void checkCallable(final Callable<T> callable) {
+ Objects.requireNonNull(callable, "callable");
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java
new file mode 100644
index 000000000..dc798e6f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreaker.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+/**
+ * An interface describing a <a
+ * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> component.
+ *
+ * <p>
+ * A <em>circuit breaker</em> can be used to protect an application against unreliable
+ * services or unexpected load. It typically monitors a specific resource. As long as this
+ * resource works as expected, it stays in state <em>closed</em>, meaning that the
+ * resource can be used. If problems are encountered when using the resource, the circuit
+ * breaker can switch into state <em>open</em>; then access to this resource is
+ * prohibited. Depending on a concrete implementation, it is possible that the circuit
+ * breaker switches back to state <em>closed</em> when the resource becomes available
+ * again.
+ * </p>
+ * <p>
+ * This interface defines a generic protocol of a circuit breaker component. It should be
+ * sufficiently generic to be applied to multiple different use cases.
+ * </p>
+ *
+ * @param <T> the type of the value monitored by this circuit breaker
+ * @since 3.5
+ */
+public interface CircuitBreaker<T> {
+ /**
+ * Returns the current open state of this circuit breaker. A return value of
+ * <strong>true</strong> means that the circuit breaker is currently open indicating a
+ * problem in the monitored sub system.
+ *
+ * @return the current open state of this circuit breaker
+ */
+ boolean isOpen();
+
+ /**
+ * Returns the current closed state of this circuit breaker. A return value of
+ * <strong>true</strong> means that the circuit breaker is currently closed. This
+ * means that everything is okay with the monitored sub system.
+ *
+ * @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 <em>closed</em>; a value
+ * of <strong>true</strong> typically means that the current operation can continue.
+ *
+ * @return <strong>true</strong> if the circuit breaker is now closed;
+ * <strong>false</strong> otherwise
+ */
+ boolean checkState();
+
+ /**
+ * Closes this circuit breaker. Its state is changed to closed. If this circuit
+ * breaker is already closed, this method has no effect.
+ */
+ 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 sub system 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
+ * value is incremented before the state check is performed.
+ *
+ * @param increment value to increment in the monitored value of the circuit breaker
+ * @return <strong>true</strong> if the circuit breaker is now closed;
+ * <strong>false</strong> otherwise
+ */
+ boolean incrementAndCheckState(T increment);
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java
new file mode 100644
index 000000000..de5022782
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/CircuitBreakingException.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+/**
+ * An exception class used for reporting runtime error conditions related to
+ * circuit breakers.
+ *
+ * @since 3.5
+ */
+public class CircuitBreakingException extends RuntimeException {
+ /**
+ * The serial version UID.
+ */
+ private static final long serialVersionUID = 1408176654686913340L;
+
+ /**
+ * Creates a new, uninitialized instance of {@link CircuitBreakingException}.
+ */
+ public CircuitBreakingException() {
+ }
+
+ /**
+ * Creates a new instance of {@link CircuitBreakingException} and initializes it with the given message and cause.
+ *
+ * @param message the error message
+ * @param cause the cause of this exception
+ */
+ public CircuitBreakingException(final String message, final Throwable cause) {
+ 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.
+ *
+ * @param cause the cause of this exception
+ */
+ public CircuitBreakingException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/Computable.java b/src/main/java/org/apache/commons/lang3/concurrent/Computable.java
new file mode 100644
index 000000000..120194256
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/Computable.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import org.apache.commons.lang3.function.FailableFunction;
+
+/**
+ * Definition of an interface for a wrapper around a calculation that takes a single parameter and returns a result.
+ *
+ * <p>This interface allows for wrapping a calculation into a class so that it maybe passed around an application.</p>
+ *
+ * <p>See also {@code FailableFunction<I, O, InterruptedException>}.</p>
+ *
+ * @param <I> the type of the input to the calculation
+ * @param <O> the type of the output of the calculation
+ * @see FailableFunction
+ * @since 3.6
+ */
+public interface Computable<I, O> {
+
+ /**
+ * This method carries out the given operation with the provided argument.
+ *
+ * @param arg
+ * the argument for the calculation
+ * @return the result of the calculation
+ * @throws InterruptedException
+ * thrown if the calculation is interrupted
+ */
+ O compute(I arg) throws InterruptedException;
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java
new file mode 100644
index 000000000..87cc23899
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * An exception class used for reporting error conditions related to accessing data of background tasks.
+ *
+ * <p>
+ * The purpose of this exception class is analogous to the default JDK exception class {@link ExecutionException}, i.e.
+ * it wraps an exception that occurred during the execution of a task. However, in contrast to
+ * {@link ExecutionException}, it wraps only checked exceptions. Runtime exceptions are thrown directly.
+ * </p>
+ *
+ * @since 3.0
+ */
+public class ConcurrentException extends Exception {
+ /**
+ * The serial version UID.
+ */
+ private static final long serialVersionUID = 6622707671812226130L;
+
+ /**
+ * Creates a new, uninitialized instance of {@link ConcurrentException}.
+ */
+ 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.
+ *
+ * @param msg the error message
+ * @param cause the cause of this exception
+ * @throws IllegalArgumentException if the cause is not a checked exception
+ */
+ public ConcurrentException(final String msg, final Throwable cause) {
+ super(msg, ConcurrentUtils.checkedException(cause));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java
new file mode 100644
index 000000000..d47ccb2ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentInitializer.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+/**
+ * Definition of an interface for the thread-safe initialization of objects.
+ *
+ * <p>
+ * The idea behind this interface is to provide access to an object in a
+ * thread-safe manner. A {@link ConcurrentInitializer} can be passed to multiple
+ * threads which can all access the object produced by the initializer. Through
+ * the {@link #get()} method the object can be queried.
+ * </p>
+ * <p>
+ * Concrete implementations of this interface will use different strategies for
+ * the creation of the managed object, e.g. lazy initialization or
+ * initialization in a background thread. This is completely transparent to
+ * client code, so it is possible to change the initialization strategy without
+ * affecting clients.
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public interface ConcurrentInitializer<T> {
+ /**
+ * Returns the fully initialized object produced by this {@code
+ * ConcurrentInitializer}. A concrete implementation here returns the
+ * results of the initialization process. This method may block until
+ * results are available. Typically, once created the result object is
+ * always the same.
+ *
+ * @return the object created by this {@link ConcurrentException}
+ * @throws ConcurrentException if an error occurred during initialization of
+ * the object
+ */
+ T get() throws ConcurrentException;
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java
new file mode 100644
index 000000000..9cf5bda22
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentRuntimeException.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+/**
+ * An exception class used for reporting runtime error conditions related to
+ * accessing data of background tasks.
+ *
+ * <p>
+ * This class is an analogue of the {@link ConcurrentException} exception class.
+ * However, it is a runtime exception and thus does not need explicit catch
+ * clauses. Some methods of {@link ConcurrentUtils} throw {@code
+ * ConcurrentRuntimeException} exceptions rather than
+ * {@link ConcurrentException} exceptions. They can be used by client code that
+ * does not want to be bothered with checked exceptions.
+ * </p>
+ *
+ * @since 3.0
+ */
+public class ConcurrentRuntimeException extends RuntimeException {
+ /**
+ * The serial version UID.
+ */
+ private static final long serialVersionUID = -6582182735562919670L;
+
+ /**
+ * Creates a new, uninitialized instance of {@code
+ * ConcurrentRuntimeException}.
+ */
+ 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.
+ *
+ * @param msg the error message
+ * @param cause the cause of this exception
+ * @throws IllegalArgumentException if the cause is not a checked exception
+ */
+ public ConcurrentRuntimeException(final String msg, final Throwable cause) {
+ super(msg, 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
new file mode 100644
index 000000000..50917d713
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * An utility class providing functionality related to the {@code
+ * java.util.concurrent} package.
+ *
+ * @since 3.0
+ */
+public class ConcurrentUtils {
+
+ /**
+ * Private constructor so that no instances can be created. This class
+ * contains only static utility methods.
+ */
+ private ConcurrentUtils() {
+ }
+
+ /**
+ * Inspects the cause of the specified {@link ExecutionException} and
+ * creates a {@link ConcurrentException} with the checked cause if
+ * necessary. This method performs the following checks on the cause of the
+ * passed in exception:
+ * <ul>
+ * <li>If the passed in exception is <b>null</b> or the cause is
+ * <b>null</b>, this method returns <b>null</b>.</li>
+ * <li>If the cause is a runtime exception, it is directly thrown.</li>
+ * <li>If the cause is an error, it is directly thrown, too.</li>
+ * <li>In any other case the cause is a checked exception. The method then
+ * creates a {@link ConcurrentException}, initializes it with the cause, and
+ * returns it.</li>
+ * </ul>
+ *
+ * @param ex the exception to be processed
+ * @return a {@link ConcurrentException} with the checked cause
+ */
+ public static ConcurrentException extractCause(final ExecutionException ex) {
+ if (ex == null || ex.getCause() == null) {
+ return null;
+ }
+
+ throwCause(ex);
+ return new ConcurrentException(ex.getMessage(), ex.getCause());
+ }
+
+ /**
+ * Inspects the cause of the specified {@link ExecutionException} and
+ * creates a {@link ConcurrentRuntimeException} with the checked cause if
+ * necessary. This method works exactly like
+ * {@link #extractCause(ExecutionException)}. The only difference is that
+ * the cause of the specified {@link ExecutionException} is extracted as a
+ * runtime exception. This is an alternative for client code that does not
+ * want to deal with checked exceptions.
+ *
+ * @param ex the exception to be processed
+ * @return a {@link ConcurrentRuntimeException} with the checked cause
+ */
+ public static ConcurrentRuntimeException extractCauseUnchecked(
+ final ExecutionException ex) {
+ if (ex == null || ex.getCause() == null) {
+ return null;
+ }
+
+ throwCause(ex);
+ return new ConcurrentRuntimeException(ex.getMessage(), ex.getCause());
+ }
+
+ /**
+ * Handles the specified {@link ExecutionException}. This method calls
+ * {@link #extractCause(ExecutionException)} for obtaining the cause of the
+ * exception - which might already cause an unchecked exception or an error
+ * being thrown. If the cause is a checked exception however, it is wrapped
+ * in a {@link ConcurrentException}, which is thrown. If the passed in
+ * exception is <b>null</b> or has no cause, the method simply returns
+ * without throwing an exception.
+ *
+ * @param ex the exception to be handled
+ * @throws ConcurrentException if the cause of the {@code
+ * ExecutionException} is a checked exception
+ */
+ public static void handleCause(final ExecutionException ex)
+ throws ConcurrentException {
+ final ConcurrentException cex = extractCause(ex);
+
+ if (cex != null) {
+ throw cex;
+ }
+ }
+
+ /**
+ * Handles the specified {@link ExecutionException} and transforms it into a
+ * runtime exception. This method works exactly like
+ * {@link #handleCause(ExecutionException)}, but instead of a
+ * {@link ConcurrentException} it throws a
+ * {@link ConcurrentRuntimeException}. This is an alternative for client
+ * code that does not want to deal with checked exceptions.
+ *
+ * @param ex the exception to be handled
+ * @throws ConcurrentRuntimeException if the cause of the {@code
+ * ExecutionException} is a checked exception; this exception is then
+ * wrapped in the thrown runtime exception
+ */
+ public static void handleCauseUnchecked(final ExecutionException ex) {
+ final ConcurrentRuntimeException crex = extractCauseUnchecked(ex);
+
+ if (crex != null) {
+ throw crex;
+ }
+ }
+
+ /**
+ * 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(ex != null && !(ex instanceof RuntimeException)
+ && !(ex instanceof Error), "Not a checked exception: " + ex);
+
+ return ex;
+ }
+
+ /**
+ * Tests whether the cause of the specified {@link ExecutionException}
+ * should be thrown and does it if necessary.
+ *
+ * @param ex the exception in question
+ */
+ private static void throwCause(final ExecutionException ex) {
+ if (ex.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) ex.getCause();
+ }
+
+ if (ex.getCause() instanceof Error) {
+ throw (Error) ex.getCause();
+ }
+ }
+
+ /**
+ * Invokes the specified {@link ConcurrentInitializer} and returns the
+ * object produced by the initializer. This method just invokes the {@code
+ * get()} method of the given {@link ConcurrentInitializer}. It is
+ * <b>null</b>-safe: if the argument is <b>null</b>, result is also
+ * <b>null</b>.
+ *
+ * @param <T> the type of the object produced by the initializer
+ * @param initializer the {@link ConcurrentInitializer} to be invoked
+ * @return the object managed by the {@link ConcurrentInitializer}
+ * @throws ConcurrentException if the {@link ConcurrentInitializer} throws
+ * an exception
+ */
+ public static <T> T initialize(final ConcurrentInitializer<T> initializer)
+ throws ConcurrentException {
+ return initializer != null ? initializer.get() : null;
+ }
+
+ /**
+ * Invokes the specified {@link ConcurrentInitializer} and transforms
+ * occurring exceptions to runtime exceptions. This method works like
+ * {@link #initialize(ConcurrentInitializer)}, but if the {@code
+ * ConcurrentInitializer} throws a {@link ConcurrentException}, it is
+ * caught, and the cause is wrapped in a {@link ConcurrentRuntimeException}.
+ * So client code does not have to deal with checked exceptions.
+ *
+ * @param <T> the type of the object produced by the initializer
+ * @param initializer the {@link ConcurrentInitializer} to be invoked
+ * @return the object managed by the {@link ConcurrentInitializer}
+ * @throws ConcurrentRuntimeException if the initializer throws an exception
+ */
+ public static <T> T initializeUnchecked(final ConcurrentInitializer<T> initializer) {
+ try {
+ return initialize(initializer);
+ } catch (final ConcurrentException cex) {
+ throw new ConcurrentRuntimeException(cex.getCause());
+ }
+ }
+
+ /**
+ * Puts a value in the specified {@link ConcurrentMap} if the key is not yet
+ * present. This method works similar to the {@code putIfAbsent()} method of
+ * the {@link ConcurrentMap} interface, but the value returned is different.
+ * Basically, this method is equivalent to the following code fragment:
+ *
+ * <pre>
+ * if (!map.containsKey(key)) {
+ * map.put(key, value);
+ * return value;
+ * } else {
+ * return map.get(key);
+ * }
+ * </pre>
+ *
+ * <p>
+ * except that the action is performed atomically. So this method always
+ * returns the value which is stored in the map.
+ * </p>
+ * <p>
+ * This method is <b>null</b>-safe: It accepts a <b>null</b> map as input
+ * without throwing an exception. In this case the return value is
+ * <b>null</b>, too.
+ * </p>
+ *
+ * @param <K> the type of the keys of the map
+ * @param <V> 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 value the value to be added
+ * @return the value stored in the map after this operation
+ */
+ public static <K, V> V putIfAbsent(final ConcurrentMap<K, V> map, final K key, final V value) {
+ if (map == null) {
+ return null;
+ }
+
+ final V result = map.putIfAbsent(key, value);
+ return result != null ? result : 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 <b>null</b>; in this
+ * case this method simply returns <b>null</b>.
+ *
+ * @param <K> the type of the keys of the map
+ * @param <V> 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 <K, V> V createIfAbsent(final ConcurrentMap<K, V> map, final K key,
+ final ConcurrentInitializer<V> 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 <K> the type of the keys of the map
+ * @param <V> 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 <K, V> V createIfAbsentUnchecked(final ConcurrentMap<K, V> map,
+ final K key, final ConcurrentInitializer<V> 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.
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @param <T> 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 <T> Future<T> constantFuture(final T value) {
+ return new ConstantFuture<>(value);
+ }
+
+ /**
+ * A specialized {@link Future} implementation which wraps a constant value.
+ * @param <T> the type of the value wrapped by this class
+ */
+ static final class ConstantFuture<T> implements Future<T> {
+ /** 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 <b>null</b>)
+ */
+ ConstantFuture(final T value) {
+ this.value = value;
+ }
+
+ /**
+ * {@inheritDoc} This implementation always returns <b>true</b> 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 <b>false</b>; 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 <b>false</b>.
+ */
+ @Override
+ public boolean cancel(final boolean mayInterruptIfRunning) {
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java
new file mode 100644
index 000000000..ea6c6a502
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.Objects;
+
+/**
+ * A very simple implementation of the {@link ConcurrentInitializer} interface
+ * which always returns the same object.
+ *
+ * <p>
+ * An instance of this class is passed a reference to an object when it is
+ * constructed. The {@link #get()} method just returns this object. No
+ * synchronization is required.
+ * </p>
+ * <p>
+ * This class is useful for instance for unit testing or in cases where a
+ * specific object has to be passed to an object which expects a
+ * {@link ConcurrentInitializer}.
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer
+ */
+public class ConstantInitializer<T> implements ConcurrentInitializer<T> {
+
+ /** Constant for the format of the string representation. */
+ private static final String FMT_TO_STRING = "ConstantInitializer@%d [ object = %s ]";
+
+ /** Stores the managed object. */
+ private final T object;
+
+ /**
+ * Creates a new instance of {@link ConstantInitializer} and initializes it
+ * with the object to be managed. The {@code get()} method will always
+ * return the object passed here. This class does not place any restrictions
+ * on the object. It may be <b>null</b>, then {@code get()} will return
+ * <b>null</b>, too.
+ *
+ * @param obj the object to be managed by this initializer
+ */
+ public ConstantInitializer(final T obj) {
+ 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();
+ }
+
+ /**
+ * 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
+ * <b>true</b> if and only if the passed in object is an instance of
+ * {@link ConstantInitializer} which refers to an object equals to the
+ * object managed by this instance.
+ *
+ * @param obj the object to compare to
+ * @return a flag whether the objects are equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ConstantInitializer<?>)) {
+ return false;
+ }
+
+ final ConstantInitializer<?> c = (ConstantInitializer<?>) obj;
+ return Objects.equals(getObject(), c.getObject());
+ }
+
+ /**
+ * Returns a string representation for this object. This string also
+ * contains a string representation of the object managed by this
+ * initializer.
+ *
+ * @return a string for this object
+ */
+ @Override
+ public String toString() {
+ return String.format(FMT_TO_STRING, Integer.valueOf(System.identityHashCode(this)),
+ String.valueOf(getObject()));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java b/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java
new file mode 100644
index 000000000..632396f09
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/EventCountCircuitBreaker.java
@@ -0,0 +1,565 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.beans.PropertyChangeListener;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A simple implementation of the <a
+ * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
+ * that counts specific events.
+ *
+ * <p>
+ * A <em>circuit breaker</em> can be used to protect an application against unreliable
+ * services or unexpected load. A newly created {@link EventCountCircuitBreaker} object is
+ * initially in state <em>closed</em> meaning that no problem has been detected. When the
+ * application encounters specific events (like errors or service timeouts), it tells the
+ * circuit breaker to increment an internal counter. If the number of events reported in a
+ * specific time interval exceeds a configurable threshold, the circuit breaker changes
+ * into state <em>open</em>. This means that there is a problem with the associated sub
+ * system; the application should no longer call it, but give it some time to settle down.
+ * The circuit breaker can be configured to switch back to <em>closed</em> state after a
+ * certain time frame if the number of events received goes below a threshold.
+ * </p>
+ * <p>
+ * When a {@link EventCountCircuitBreaker} object is constructed the following parameters
+ * can be provided:
+ * </p>
+ * <ul>
+ * <li>A threshold for the number of events that causes a state transition to
+ * <em>open</em> state. If more events are received in the configured check interval, the
+ * circuit breaker switches to <em>open</em> state.</li>
+ * <li>The interval for checks whether the circuit breaker should open. So it is possible
+ * to specify something like "The circuit breaker should open if more than 10 errors are
+ * encountered in a minute."</li>
+ * <li>The same parameters can be specified for automatically closing the circuit breaker
+ * again, as in "If the number of requests goes down to 100 per minute, the circuit
+ * breaker should close itself again". Depending on the use case, it may make sense to use
+ * a slightly lower threshold for closing the circuit breaker than for opening it to avoid
+ * continuously flipping when the number of events received is close to the threshold.</li>
+ * </ul>
+ * <p>
+ * This class supports the following typical use cases:
+ * </p>
+ * <p>
+ * <strong>Protecting against load peaks</strong>
+ * </p>
+ * <p>
+ * Imagine you have a server which can handle a certain number of requests per minute.
+ * Suddenly, the number of requests increases significantly - maybe because a connected
+ * partner system is going mad or due to a denial of service attack. A
+ * {@link EventCountCircuitBreaker} can be configured to stop the application from
+ * processing requests when a sudden peak load is detected and to start request processing
+ * again when things calm down. The following code fragment shows a typical example of
+ * such a scenario. Here the {@link EventCountCircuitBreaker} allows up to 1000 requests
+ * per minute before it interferes. When the load goes down again to 800 requests per
+ * second it switches back to state <em>closed</em>:
+ * </p>
+ *
+ * <pre>
+ * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(1000, 1, TimeUnit.MINUTE, 800);
+ * ...
+ * public void handleRequest(Request request) {
+ * if (breaker.incrementAndCheckState()) {
+ * // actually handle this request
+ * } else {
+ * // do something else, e.g. send an error code
+ * }
+ * }
+ * </pre>
+ * <p>
+ * <strong>Deal with an unreliable service</strong>
+ * </p>
+ * <p>
+ * In this scenario, an application uses an external service which may fail from time to
+ * time. If there are too many errors, the service is considered down and should not be
+ * called for a while. This can be achieved using the following pattern - in this concrete
+ * example we accept up to 5 errors in 2 minutes; if this limit is reached, the service is
+ * given a rest time of 10 minutes:
+ * </p>
+ *
+ * <pre>
+ * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(5, 2, TimeUnit.MINUTE, 5, 10, TimeUnit.MINUTE);
+ * ...
+ * public void handleRequest(Request request) {
+ * if (breaker.checkState()) {
+ * try {
+ * service.doSomething();
+ * } catch (ServiceException ex) {
+ * breaker.incrementAndCheckState();
+ * }
+ * } else {
+ * // return an error code, use an alternative service, etc.
+ * }
+ * }
+ * </pre>
+ * <p>
+ * In addition to automatic state transitions, the state of a circuit breaker can be
+ * changed manually using the methods {@link #open()} and {@link #close()}. It is also
+ * possible to register {@link PropertyChangeListener} objects that get notified whenever
+ * a state transition occurs. This is useful, for instance to directly react on a freshly
+ * detected error condition.
+ * </p>
+ * <p>
+ * <em>Implementation notes:</em>
+ * </p>
+ * <ul>
+ * <li>This implementation uses non-blocking algorithms to update the internal counter and
+ * state. This should be pretty efficient if there is not too much contention.</li>
+ * <li>This implementation is not intended to operate as a high-precision timer in very
+ * short check intervals. It is deliberately kept simple to avoid complex and
+ * time-consuming state checks. It should work well in time intervals from a few seconds
+ * up to minutes and longer. If the intervals become too short, there might be race
+ * conditions causing spurious state transitions.</li>
+ * <li>The handling of check intervals is a bit simplistic. Therefore, there is no
+ * guarantee that the circuit breaker is triggered at a specific point in time; there may
+ * be some delay (less than a check interval).</li>
+ * </ul>
+ * @since 3.5
+ */
+public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
+
+ /** A map for accessing the strategy objects for the different states. */
+ private static final Map<State, StateStrategy> STRATEGY_MAP = createStrategyMap();
+
+ /** Stores information about the current check interval. */
+ private final AtomicReference<CheckIntervalData> checkIntervalData;
+
+ /** The threshold for opening the circuit breaker. */
+ private final int openingThreshold;
+
+ /** The time interval for opening the circuit breaker. */
+ private final long openingInterval;
+
+ /** The threshold for closing the circuit breaker. */
+ private final int closingThreshold;
+
+ /** The time interval for closing the circuit breaker. */
+ private final long closingInterval;
+
+ /**
+ * 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
+ * intervals.
+ *
+ * @param openingThreshold the threshold 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
+ * @param openingInterval the interval for opening the circuit breaker
+ * @param openingUnit the {@link TimeUnit} defining the opening interval
+ * @param closingThreshold the threshold 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
+ * @param closingInterval the interval for closing the circuit breaker
+ * @param closingUnit the {@link TimeUnit} defining the closing interval
+ */
+ public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
+ final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
+ final TimeUnit closingUnit) {
+ checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
+ this.openingThreshold = openingThreshold;
+ this.openingInterval = openingUnit.toNanos(openingInterval);
+ this.closingThreshold = closingThreshold;
+ this.closingInterval = closingUnit.toNanos(closingInterval);
+ }
+
+ /**
+ * 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} 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;
+ }
+
+ /**
+ * {@inheritDoc} This implementation checks the internal event counter against the
+ * threshold values and the check intervals. This may cause a state change of this
+ * circuit breaker.
+ */
+ @Override
+ public boolean checkState() {
+ return performStateCheck(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean incrementAndCheckState(final Integer increment) {
+ return performStateCheck(increment);
+ }
+
+ /**
+ * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
+ * circuit breaker. This method works like {@link #checkState()}, but the monitored
+ * value is incremented before the state check is performed.
+ *
+ * @return <strong>true</strong> if the circuit breaker is now closed;
+ * <strong>false</strong> otherwise
+ */
+ public boolean incrementAndCheckState() {
+ return incrementAndCheckState(1);
+ }
+
+ /**
+ * {@inheritDoc} This circuit breaker may close itself again if the number of events
+ * received during a check interval goes below the closing threshold. If this circuit
+ * breaker is already open, this method has no effect, except that a new check
+ * interval is started.
+ */
+ @Override
+ public void open() {
+ super.open();
+ 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.
+ *
+ * @param increment the increment for the internal counter
+ * @return a flag whether the circuit breaker is now closed
+ */
+ private boolean performStateCheck(final int increment) {
+ CheckIntervalData currentData;
+ CheckIntervalData nextData;
+ State currentState;
+
+ do {
+ final long time = nanoTime();
+ currentState = state.get();
+ currentData = checkIntervalData.get();
+ nextData = nextCheckIntervalData(increment, currentData, currentState, time);
+ } while (!updateCheckIntervalData(currentData, nextData));
+
+ // This might cause a race condition if other changes happen in between!
+ // Refer to the header comment!
+ if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
+ currentState = currentState.oppositeState();
+ changeStateAndStartNewCheckInterval(currentState);
+ }
+ return !isOpen(currentState);
+ }
+
+ /**
+ * Updates the {@link CheckIntervalData} object. The current data object is replaced
+ * by the one modified by the last check. The return value indicates whether this was
+ * successful. If it is <strong>false</strong>, another thread interfered, and the
+ * whole operation has to be redone.
+ *
+ * @param currentData the current check data object
+ * @param nextData the replacing check data object
+ * @return a flag whether the update was successful
+ */
+ private boolean updateCheckIntervalData(final CheckIntervalData currentData,
+ final CheckIntervalData nextData) {
+ return currentData == nextData
+ || 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<State, StateStrategy> createStrategyMap() {
+ final Map<State, StateStrategy> 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 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 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 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
new file mode 100644
index 000000000..5adaf753d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/FutureTasks.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Consists of utility methods that work with {@link FutureTask}.
+ *
+ * @since 3.13.0
+ */
+public class FutureTasks {
+
+ private FutureTasks() {
+ // No instances needed.
+ }
+
+ /**
+ * Creates a {@link FutureTask} and runs the given {@link Callable}.
+ *
+ * @param <V> The result type returned by this FutureTask's {@code get} methods.
+ * @param callable the Callable task.
+ * @return a new FutureTask.
+ */
+ public static <V> FutureTask<V> run(final Callable<V> callable) {
+ final FutureTask<V> futureTask = new FutureTask<>(callable);
+ futureTask.run();
+ return futureTask;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java
new file mode 100644
index 000000000..32b8db5d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/LazyInitializer.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+/**
+ * This class provides a generic implementation of the lazy initialization
+ * pattern.
+ *
+ * <p>
+ * Sometimes an application has to deal with an object only under certain
+ * circumstances, e.g. when the user selects a specific menu item or if a
+ * special event is received. If the creation of the object is costly or the
+ * consumption of memory or other system resources is significant, it may make
+ * sense to defer the creation of this object until it is really needed. This is
+ * a use case for the lazy initialization pattern.
+ * </p>
+ * <p>
+ * This abstract base class provides an implementation of the double-check idiom
+ * for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd
+ * edition, item 71. The class already implements all necessary synchronization.
+ * A concrete subclass has to implement the {@code initialize()} method, which
+ * actually creates the wrapped data object.
+ * </p>
+ * <p>
+ * As an usage example consider that we have a class {@code ComplexObject} whose
+ * instantiation is a complex operation. In order to apply lazy initialization
+ * to this class, a subclass of {@link LazyInitializer} has to be created:
+ * </p>
+ *
+ * <pre>
+ * public class ComplexObjectInitializer extends LazyInitializer&lt;ComplexObject&gt; {
+ * &#064;Override
+ * protected ComplexObject initialize() {
+ * return new ComplexObject();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * Access to the data object is provided through the {@code get()} method. So,
+ * code that wants to obtain the {@code ComplexObject} instance would simply
+ * look like this:
+ * </p>
+ *
+ * <pre>
+ * // Create an instance of the lazy initializer
+ * ComplexObjectInitializer initializer = new ComplexObjectInitializer();
+ * ...
+ * // When the object is actually needed:
+ * ComplexObject cobj = initializer.get();
+ * </pre>
+ *
+ * <p>
+ * If multiple threads call the {@code get()} method when the object has not yet
+ * been created, they are blocked until initialization completes. The algorithm
+ * guarantees that only a single instance of the wrapped object class is
+ * created, which is passed to all callers. Once initialized, calls to the
+ * {@code get()} method are pretty fast because no synchronization is needed
+ * (only an access to a <b>volatile</b> member field).
+ * </p>
+ *
+ * @since 3.0
+ * @param <T> the type of the object managed by this initializer class
+ */
+public abstract class LazyInitializer<T> implements ConcurrentInitializer<T> {
+
+ private static final Object NO_INIT = new Object();
+
+ @SuppressWarnings("unchecked")
+ // Stores the managed object.
+ private volatile T object = (T) NO_INIT;
+
+ /**
+ * Returns the object wrapped by this instance. On first access the object
+ * is created. After that it is cached and can be accessed pretty fast.
+ *
+ * @return the object initialized by this {@link LazyInitializer}
+ * @throws ConcurrentException if an error occurred during initialization of
+ * the object
+ */
+ @Override
+ public T get() throws ConcurrentException {
+ // use a temporary variable to reduce the number of reads of the
+ // volatile field
+ T result = object;
+
+ if (result == NO_INIT) {
+ synchronized (this) {
+ result = object;
+ if (result == NO_INIT) {
+ object = result = initialize();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates and initializes the object managed by this {@code
+ * LazyInitializer}. This method is called by {@link #get()} when the object
+ * is accessed for the first time. An implementation can focus on the
+ * creation of the object. No synchronization is needed, as this is already
+ * handled by {@code get()}.
+ *
+ * @return the managed data object
+ * @throws ConcurrentException if an error occurs during object creation
+ */
+ protected abstract T initialize() throws ConcurrentException;
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java b/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java
new file mode 100644
index 000000000..dd0e3ef22
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+
+/**
+ * Definition of an interface for a wrapper around a calculation that takes a single parameter and returns a result. The
+ * results for the calculation will be cached for future requests.
+ *
+ * <p>
+ * This is not a fully functional cache, there is no way of limiting or removing results once they have been generated.
+ * However, it is possible to get the implementation to regenerate the result for a given parameter, if an error was
+ * thrown during the previous calculation, by setting the option during the construction of the class. If this is not
+ * set the class will return the cached exception.
+ * </p>
+ * <p>
+ * Thanks should go to Brian Goetz, Tim Peierls and the members of JCP JSR-166 Expert Group for coming up with the
+ * original implementation of the class. It was also published within Java Concurrency in Practice as a sample.
+ * </p>
+ *
+ * @param <I> the type of the input to the calculation
+ * @param <O> the type of the output of the calculation
+ *
+ * @since 3.6
+ */
+public class Memoizer<I, O> implements Computable<I, O> {
+
+ private final ConcurrentMap<I, Future<O>> cache = new ConcurrentHashMap<>();
+ private final Function<? super I, ? extends Future<O>> mappingFunction;
+ private final boolean recalculate;
+
+ /**
+ * Constructs a Memoizer for the provided Computable calculation.
+ *
+ * <p>
+ * If a calculation throws an exception for any reason, this exception will be cached and returned for all future
+ * calls with the provided parameter.
+ * </p>
+ *
+ * @param computable the computation whose results should be memorized
+ */
+ public Memoizer(final Computable<I, O> computable) {
+ this(computable, false);
+ }
+
+ /**
+ * Constructs a Memoizer for the provided Computable calculation, with the option of whether a Computation that
+ * experiences an error should recalculate on subsequent calls or return the same cached exception.
+ *
+ * @param computable the computation whose results should be memorized
+ * @param recalculate determines whether the computation should be recalculated on subsequent calls if the previous call
+ * failed
+ */
+ public Memoizer(final Computable<I, O> computable, final boolean recalculate) {
+ this.recalculate = recalculate;
+ this.mappingFunction = k -> FutureTasks.run(() -> computable.compute(k));
+ }
+
+ /**
+ * Constructs a Memoizer for the provided Function calculation.
+ *
+ * <p>
+ * If a calculation throws an exception for any reason, this exception will be cached and returned for all future
+ * calls with the provided parameter.
+ * </p>
+ *
+ * @param function the function whose results should be memorized
+ * @since 2.13.0
+ */
+ public Memoizer(final Function<I, O> function) {
+ this(function, false);
+ }
+
+ /**
+ * Constructs a Memoizer for the provided Function calculation, with the option of whether a Function that
+ * experiences an error should recalculate on subsequent calls or return the same cached exception.
+ *
+ * @param function the computation whose results should be memorized
+ * @param recalculate determines whether the computation should be recalculated on subsequent calls if the previous call
+ * failed
+ * @since 2.13.0
+ */
+ public Memoizer(final Function<I, O> function, final boolean recalculate) {
+ this.recalculate = recalculate;
+ this.mappingFunction = k -> FutureTasks.run(() -> function.apply(k));
+ }
+
+ /**
+ * This method will return the result of the calculation and cache it, if it has not previously been calculated.
+ *
+ * <p>
+ * This cache will also cache exceptions that occur during the computation if the {@code recalculate} parameter in the
+ * constructor was set to {@code false}, or not set. Otherwise, if an exception happened on the previous calculation,
+ * the method will attempt again to generate a value.
+ * </p>
+ *
+ * @param arg the argument for the calculation
+ * @return the result of the calculation
+ * @throws InterruptedException thrown if the calculation is interrupted
+ */
+ @Override
+ public O compute(final I arg) throws InterruptedException {
+ while (true) {
+ final Future<O> future = cache.computeIfAbsent(arg, mappingFunction);
+ try {
+ return future.get();
+ } catch (final CancellationException e) {
+ cache.remove(arg, future);
+ } catch (final ExecutionException e) {
+ if (recalculate) {
+ cache.remove(arg, future);
+ }
+ throw launderException(e.getCause());
+ }
+ }
+ }
+
+ /**
+ * This method launders a Throwable to either a RuntimeException, Error or any other Exception wrapped in an
+ * IllegalStateException.
+ *
+ * @param throwable the throwable to laundered
+ * @return a RuntimeException, Error or an IllegalStateException
+ */
+ private RuntimeException launderException(final Throwable throwable) {
+ if (throwable instanceof RuntimeException) {
+ return (RuntimeException) throwable;
+ }
+ if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ throw new IllegalStateException("Unchecked exception", throwable);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java b/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
new file mode 100644
index 000000000..4f5abaad1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
@@ -0,0 +1,337 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * A specialized {@link BackgroundInitializer} implementation that can deal with
+ * multiple background initialization tasks.
+ *
+ * <p>
+ * This class has a similar purpose as {@link BackgroundInitializer}. However,
+ * it is not limited to a single background initialization task. Rather it
+ * manages an arbitrary number of {@link BackgroundInitializer} objects,
+ * executes them, and waits until they are completely initialized. This is
+ * useful for applications that have to perform multiple initialization tasks
+ * that can run in parallel (i.e. that do not depend on each other). This class
+ * takes care about the management of an {@link ExecutorService} and shares it
+ * with the {@link BackgroundInitializer} objects it is responsible for; so the
+ * using application need not bother with these details.
+ * </p>
+ * <p>
+ * The typical usage scenario for {@link MultiBackgroundInitializer} is as
+ * follows:
+ * </p>
+ * <ul>
+ * <li>Create a new instance of the class. Optionally pass in a pre-configured
+ * {@link ExecutorService}. Alternatively {@link MultiBackgroundInitializer} can
+ * create a temporary {@link ExecutorService} and delete it after initialization
+ * is complete.</li>
+ * <li>Create specialized {@link BackgroundInitializer} objects for the
+ * initialization tasks to be performed and add them to the {@code
+ * MultiBackgroundInitializer} using the
+ * {@link #addInitializer(String, BackgroundInitializer)} method.</li>
+ * <li>After all initializers have been added, call the {@link #start()} method.
+ * </li>
+ * <li>When access to the result objects produced by the {@code
+ * BackgroundInitializer} objects is needed call the {@link #get()} method. The
+ * object returned here provides access to all result objects created during
+ * initialization. It also stores information about exceptions that have
+ * occurred.</li>
+ * </ul>
+ * <p>
+ * {@link MultiBackgroundInitializer} starts a special controller task that
+ * starts all {@link BackgroundInitializer} objects added to the instance.
+ * Before the an initializer is started it is checked whether this initializer
+ * already has an {@link ExecutorService} set. If this is the case, this {@code
+ * ExecutorService} is used for running the background task. Otherwise the
+ * current {@link ExecutorService} of this {@link MultiBackgroundInitializer} is
+ * shared with the initializer.
+ * </p>
+ * <p>
+ * The easiest way of using this class is to let it deal with the management of
+ * an {@link ExecutorService} itself: If no external {@link ExecutorService} is
+ * provided, the class creates a temporary {@link ExecutorService} (that is
+ * capable of executing all background tasks in parallel) and destroys it at the
+ * end of background processing.
+ * </p>
+ * <p>
+ * Alternatively an external {@link ExecutorService} can be provided - either at
+ * construction time or later by calling the
+ * {@link #setExternalExecutor(ExecutorService)} method. In this case all
+ * background tasks are scheduled at this external {@link ExecutorService}.
+ * <strong>Important note:</strong> When using an external {@code
+ * ExecutorService} be sure that the number of threads managed by the service is
+ * large enough. Otherwise a deadlock can happen! This is the case in the
+ * following scenario: {@link MultiBackgroundInitializer} starts a task that
+ * starts all registered {@link BackgroundInitializer} objects and waits for
+ * their completion. If for instance a single threaded {@link ExecutorService}
+ * is used, none of the background tasks can be executed, and the task created
+ * by {@link MultiBackgroundInitializer} waits forever.
+ * </p>
+ *
+ * @since 3.0
+ */
+public class MultiBackgroundInitializer
+ extends
+ BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> {
+
+ /** A map with the child initializers. */
+ private final Map<String, BackgroundInitializer<?>> childInitializers = new HashMap<>();
+
+ /**
+ * Creates a new instance of {@link MultiBackgroundInitializer}.
+ */
+ public MultiBackgroundInitializer() {
+ }
+
+ /**
+ * Creates a new instance of {@link MultiBackgroundInitializer} and
+ * initializes it with the given external {@link ExecutorService}.
+ *
+ * @param exec the {@link ExecutorService} for executing the background
+ * tasks
+ */
+ public MultiBackgroundInitializer(final ExecutorService exec) {
+ super(exec);
+ }
+
+ /**
+ * Adds a new {@link BackgroundInitializer} to this object. When this
+ * {@link MultiBackgroundInitializer} is started, the given initializer will
+ * be processed. This method must not be called after {@link #start()} has
+ * been invoked.
+ *
+ * @param name the name of the initializer (must not be <b>null</b>)
+ * @param backgroundInitializer the {@link BackgroundInitializer} to add (must not be
+ * <b>null</b>)
+ * @throws NullPointerException if either {@code name} or {@code backgroundInitializer}
+ * is {@code null}
+ * @throws IllegalStateException if {@code start()} has already been called
+ */
+ public void addInitializer(final String name, final BackgroundInitializer<?> backgroundInitializer) {
+ Objects.requireNonNull(name, "name");
+ Objects.requireNonNull(backgroundInitializer, "backgroundInitializer");
+
+ synchronized (this) {
+ if (isStarted()) {
+ throw new IllegalStateException("addInitializer() must not be called after start()!");
+ }
+ childInitializers.put(name, backgroundInitializer);
+ }
+ }
+
+ /**
+ * Returns the number of tasks needed for executing all child {@code
+ * BackgroundInitializer} objects in parallel. This implementation sums up
+ * the required tasks for all child initializers (which is necessary if one
+ * of the child initializers is itself a {@link MultiBackgroundInitializer}
+ * ). Then it adds 1 for the control task that waits for the completion of
+ * the children.
+ *
+ * @return the number of tasks required for background processing
+ */
+ @Override
+ protected int getTaskCount() {
+ return 1 + childInitializers.values().stream().mapToInt(BackgroundInitializer::getTaskCount).sum();
+ }
+
+ /**
+ * Creates the results object. This implementation starts all child {@code
+ * BackgroundInitializer} objects. Then it collects their results and
+ * creates a {@link MultiBackgroundInitializerResults} object with this
+ * data. If a child initializer throws a checked exceptions, it is added to
+ * the results object. Unchecked exceptions are propagated.
+ *
+ * @return the results object
+ * @throws Exception if an error occurs
+ */
+ @Override
+ protected MultiBackgroundInitializerResults initialize() throws Exception {
+ final Map<String, BackgroundInitializer<?>> inits;
+ synchronized (this) {
+ // create a snapshot to operate on
+ inits = new HashMap<>(childInitializers);
+ }
+
+ // start the child initializers
+ final ExecutorService exec = getActiveExecutor();
+ inits.values().forEach(bi -> {
+ if (bi.getExternalExecutor() == null) {
+ // share the executor service if necessary
+ bi.setExternalExecutor(exec);
+ }
+ bi.start();
+ });
+
+ // collect the results
+ final Map<String, Object> results = new HashMap<>();
+ final Map<String, ConcurrentException> excepts = new HashMap<>();
+ inits.forEach((k, v) -> {
+ try {
+ results.put(k, v.get());
+ } catch (final ConcurrentException cex) {
+ excepts.put(k, cex);
+ }
+ });
+
+ return new MultiBackgroundInitializerResults(inits, results, excepts);
+ }
+
+ /**
+ * 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<String, BackgroundInitializer<?>> initializers;
+
+ /** A map with the result objects. */
+ private final Map<String, Object> resultObjects;
+
+ /** A map with the exceptions. */
+ private final Map<String, ConcurrentException> 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<String, BackgroundInitializer<?>> inits,
+ final Map<String, Object> results,
+ final Map<String, ConcurrentException> 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, <b>null</b> 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
+ * <b>null</b>. 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<String> 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
new file mode 100644
index 000000000..8cfd58e9a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ThresholdCircuitBreaker.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A simple implementation of the <a
+ * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
+ * that opens if the requested increment amount is greater than a given threshold.
+ *
+ * <p>
+ * It contains an internal counter that starts in zero, and each call increments the counter by a given amount.
+ * If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state.
+ * </p>
+ *
+ * <p>
+ * An example of use case could be a memory circuit breaker.
+ * </p>
+ *
+ * <pre>
+ * long threshold = 10L;
+ * ThresholdCircuitBreaker breaker = new ThresholdCircuitBreaker(10L);
+ * ...
+ * public void handleRequest(Request request) {
+ * long memoryUsed = estimateMemoryUsage(request);
+ * if (breaker.incrementAndCheckState(memoryUsed)) {
+ * // actually handle this request
+ * } else {
+ * // do something else, e.g. send an error code
+ * }
+ * }
+ * </pre>
+ *
+ * <p>#Thread safe#</p>
+ * @since 3.5
+ */
+public class ThresholdCircuitBreaker extends AbstractCircuitBreaker<Long> {
+ /**
+ * The initial value of the internal counter.
+ */
+ private static final long INITIAL_COUNT = 0L;
+
+ /**
+ * The threshold.
+ */
+ private final long threshold;
+
+ /**
+ * Controls the amount used.
+ */
+ private final AtomicLong used;
+
+ /**
+ * Creates a new instance of {@link ThresholdCircuitBreaker} and initializes the threshold.
+ *
+ * @param threshold the threshold.
+ */
+ public ThresholdCircuitBreaker(final long threshold) {
+ this.used = new AtomicLong(INITIAL_COUNT);
+ this.threshold = threshold;
+ }
+
+ /**
+ * Gets the threshold.
+ *
+ * @return the threshold
+ */
+ public long getThreshold() {
+ return threshold;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean checkState() {
+ return isOpen();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Resets the internal counter back to its initial value (zero).</p>
+ */
+ @Override
+ public void close() {
+ super.close();
+ this.used.set(INITIAL_COUNT);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state.</p>
+ */
+ @Override
+ public boolean incrementAndCheckState(final Long increment) {
+ if (threshold == 0) {
+ open();
+ }
+
+ final long used = this.used.addAndGet(increment);
+ if (used > threshold) {
+ open();
+ }
+
+ return checkState();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
new file mode 100644
index 000000000..93b1fa385
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
@@ -0,0 +1,464 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * A specialized <em>semaphore</em> implementation that provides a number of
+ * permits in a given time frame.
+ *
+ * <p>
+ * This class is similar to the {@code java.util.concurrent.Semaphore} class
+ * provided by the JDK in that it manages a configurable number of permits.
+ * Using the {@link #acquire()} method a permit can be requested by a thread.
+ * However, there is an additional timing dimension: there is no {@code
+ * release()} method for freeing a permit, but all permits are automatically
+ * released at the end of a configurable time frame. If a thread calls
+ * {@link #acquire()} and the available permits are already exhausted for this
+ * time frame, the thread is blocked. When the time frame ends all permits
+ * requested so far are restored, and blocking threads are waked up again, so
+ * that they can try to acquire a new permit. This basically means that in the
+ * specified time frame only the given number of operations is possible.
+ * </p>
+ * <p>
+ * A use case for this class is to artificially limit the load produced by a
+ * process. As an example consider an application that issues database queries
+ * on a production system in a background process to gather statistical
+ * information. This background processing should not produce so much database
+ * load that the functionality and the performance of the production system are
+ * impacted. Here a {@link TimedSemaphore} could be installed to guarantee that
+ * only a given number of database queries are issued per second.
+ * </p>
+ * <p>
+ * A thread class for performing database queries could look as follows:
+ * </p>
+ *
+ * <pre>
+ * public class StatisticsThread extends Thread {
+ * // The semaphore for limiting database load.
+ * private final TimedSemaphore semaphore;
+ * // Create an instance and set the semaphore
+ * public StatisticsThread(TimedSemaphore timedSemaphore) {
+ * semaphore = timedSemaphore;
+ * }
+ * // Gather statistics
+ * public void run() {
+ * try {
+ * while (true) {
+ * semaphore.acquire(); // limit database load
+ * performQuery(); // issue a query
+ * }
+ * } catch(InterruptedException) {
+ * // fall through
+ * }
+ * }
+ * ...
+ * }
+ * </pre>
+ *
+ * <p>
+ * The following code fragment shows how a {@link TimedSemaphore} is created
+ * that allows only 10 operations per second and passed to the statistics
+ * thread:
+ * </p>
+ *
+ * <pre>
+ * TimedSemaphore sem = new TimedSemaphore(1, TimeUnit.SECOND, 10);
+ * StatisticsThread thread = new StatisticsThread(sem);
+ * thread.start();
+ * </pre>
+ *
+ * <p>
+ * When creating an instance the time period for the semaphore must be
+ * specified. {@link TimedSemaphore} uses an executor service with a
+ * corresponding period to monitor this interval. The {@code
+ * ScheduledExecutorService} to be used for this purpose can be provided at
+ * construction time. Alternatively the class creates an internal executor
+ * service.
+ * </p>
+ * <p>
+ * Client code that uses {@link TimedSemaphore} has to call the
+ * {@link #acquire()} method in each processing step. {@link TimedSemaphore}
+ * keeps track of the number of invocations of the {@link #acquire()} method and
+ * blocks the calling thread if the counter exceeds the limit specified. When
+ * the timer signals the end of the time period the counter is reset and all
+ * waiting threads are released. Then another cycle can start.
+ * </p>
+ * <p>
+ * An alternative to {@code acquire()} is the {@link #tryAcquire()} method. This
+ * method checks whether the semaphore is under the specified limit and
+ * increases the internal counter if this is the case. The return value is then
+ * <strong>true</strong>, and the calling thread can continue with its action.
+ * If the semaphore is already at its limit, {@code tryAcquire()} immediately
+ * returns <strong>false</strong> without blocking; the calling thread must
+ * then abort its action. This usage scenario prevents blocking of threads.
+ * </p>
+ * <p>
+ * It is possible to modify the limit at any time using the
+ * {@link #setLimit(int)} method. This is useful if the load produced by an
+ * operation has to be adapted dynamically. In the example scenario with the
+ * thread collecting statistics it may make sense to specify a low limit during
+ * day time while allowing a higher load in the night time. Reducing the limit
+ * takes effect immediately by blocking incoming callers. If the limit is
+ * increased, waiting threads are not released immediately, but wake up when the
+ * timer runs out. Then, in the next period more processing steps can be
+ * performed without blocking. By setting the limit to 0 the semaphore can be
+ * switched off: in this mode the {@link #acquire()} method never blocks, but
+ * lets all callers pass directly.
+ * </p>
+ * <p>
+ * When the {@link TimedSemaphore} is no more needed its {@link #shutdown()}
+ * method should be called. This causes the periodic task that monitors the time
+ * interval to be canceled. If the {@link ScheduledExecutorService} has been
+ * created by the semaphore at construction time, it is also shut down.
+ * resources. After that {@link #acquire()} must not be called any more.
+ * </p>
+ *
+ * @since 3.0
+ */
+public class TimedSemaphore {
+ /**
+ * Constant for a value representing no limit. If the limit is set to a
+ * value less or equal this constant, the {@link TimedSemaphore} will be
+ * effectively switched off.
+ */
+ public static final int NO_LIMIT = 0;
+
+ /** Constant for the thread pool size for the executor. */
+ private static final int THREAD_POOL_SIZE = 1;
+
+ /** The executor service for managing the timer thread. */
+ private final ScheduledExecutorService executorService;
+
+ /** Stores the period for this timed semaphore. */
+ private final long period;
+
+ /** The time unit for the period. */
+ private final TimeUnit unit;
+
+ /** A flag whether the executor service was created by this object. */
+ private final boolean ownExecutor;
+
+ /** A future object representing the timer task. */
+ private ScheduledFuture<?> task; // @GuardedBy("this")
+
+ /** Stores the total number of invocations of the acquire() method. */
+ private long totalAcquireCount; // @GuardedBy("this")
+
+ /**
+ * The counter for the periods. This counter is increased every time a
+ * period ends.
+ */
+ private long periodCount; // @GuardedBy("this")
+
+ /** The limit. */
+ private int limit; // @GuardedBy("this")
+
+ /** The current counter. */
+ private int acquireCount; // @GuardedBy("this")
+
+ /** The number of invocations of acquire() in the last period. */
+ private int lastCallsPerPeriod; // @GuardedBy("this")
+
+ /** A flag whether shutdown() was called. */
+ private boolean shutdown; // @GuardedBy("this")
+
+ /**
+ * Creates a new instance of {@link TimedSemaphore} and initializes it with
+ * the given time period and the limit.
+ *
+ * @param timePeriod the time period
+ * @param timeUnit the unit for the period
+ * @param limit the limit for the semaphore
+ * @throws IllegalArgumentException if the period is less or equals 0
+ */
+ public TimedSemaphore(final long timePeriod, final TimeUnit timeUnit, final int limit) {
+ this(null, timePeriod, timeUnit, limit);
+ }
+
+ /**
+ * Creates a new instance of {@link TimedSemaphore} and initializes it with
+ * an executor service, the given time period, and the limit. The executor
+ * service will be used for creating a periodic task for monitoring the time
+ * period. It can be <b>null</b>, then a default service will be created.
+ *
+ * @param service the executor service
+ * @param timePeriod the time period
+ * @param timeUnit the unit for the period
+ * @param limit the limit for the semaphore
+ * @throws IllegalArgumentException if the period is less or equals 0
+ */
+ public TimedSemaphore(final ScheduledExecutorService service, final long timePeriod,
+ final TimeUnit timeUnit, final int limit) {
+ Validate.inclusiveBetween(1, Long.MAX_VALUE, timePeriod, "Time period must be greater than 0!");
+
+ period = timePeriod;
+ unit = timeUnit;
+
+ if (service != null) {
+ executorService = service;
+ ownExecutor = false;
+ } else {
+ final ScheduledThreadPoolExecutor s = new ScheduledThreadPoolExecutor(
+ THREAD_POOL_SIZE);
+ s.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+ s.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ executorService = s;
+ ownExecutor = true;
+ }
+
+ 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 &lt;= {@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 <b>true</b>, 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
+ * {@link #shutdown()} has already been invoked, calling this method will
+ * cause an exception. The very first call of this method starts the timer
+ * task which monitors the time period set for this {@link TimedSemaphore}.
+ * From now on the semaphore is active.
+ *
+ * @throws InterruptedException if the thread gets interrupted
+ * @throws IllegalStateException if this semaphore is already shut down
+ */
+ public synchronized void acquire() throws InterruptedException {
+ prepareAcquire();
+
+ boolean canPass;
+ do {
+ canPass = acquirePermit();
+ if (!canPass) {
+ wait();
+ }
+ } while (!canPass);
+ }
+
+ /**
+ * 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
+ * <strong>true</strong>. Otherwise, this method returns immediately with the result
+ * <strong>false</strong>.
+ *
+ * @return <strong>true</strong> if a permit could be acquired; <strong>false</strong>
+ * otherwise
+ * @throws IllegalStateException if this semaphore is already shut down
+ * @since 3.5
+ */
+ public synchronized boolean tryAcquire() {
+ prepareAcquire();
+ return acquirePermit();
+ }
+
+ /**
+ * 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 number of invocations of the {@link #acquire()} method for
+ * the current period. This may be useful for testing or debugging purposes.
+ *
+ * @return the current number of {@link #acquire()} invocations
+ */
+ public synchronized int getAcquireCount() {
+ return acquireCount;
+ }
+
+ /**
+ * Returns the number of calls to the {@link #acquire()} method that can
+ * still be performed in the current period without blocking. This method
+ * can give an indication whether it is safe to call the {@link #acquire()}
+ * method without risking to be suspended. However, there is no guarantee
+ * that a subsequent call to {@link #acquire()} actually is not-blocking
+ * because in the meantime other threads may have invoked the semaphore.
+ *
+ * @return the current number of available {@link #acquire()} calls in the
+ * current period
+ */
+ public synchronized int getAvailablePermits() {
+ return getLimit() - getAcquireCount();
+ }
+
+ /**
+ * Returns the average number of successful (i.e. non-blocking)
+ * {@link #acquire()} invocations for the entire life-time of this {@code
+ * TimedSemaphore}. This method can be used for instance for statistical
+ * calculations.
+ *
+ * @return the average number of {@link #acquire()} invocations per time
+ * unit
+ */
+ public synchronized double getAverageCallsPerPeriod() {
+ return periodCount == 0 ? 0 : (double) totalAcquireCount
+ / (double) periodCount;
+ }
+
+ /**
+ * Returns the time period. This is the time monitored by this semaphore.
+ * Only a given number of invocations of the {@link #acquire()} method is
+ * possible in this period.
+ *
+ * @return the time period
+ */
+ public long getPeriod() {
+ return period;
+ }
+
+ /**
+ * Returns the time unit. This is the unit used by {@link #getPeriod()}.
+ *
+ * @return the time unit
+ */
+ public TimeUnit getUnit() {
+ return unit;
+ }
+
+ /**
+ * Returns the executor service used by this instance.
+ *
+ * @return the executor service
+ */
+ 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();
+ }
+
+ /**
+ * Prepares an acquire operation. Checks for the current state and starts the internal
+ * timer if necessary. This method must be called with the lock of this object held.
+ */
+ private void prepareAcquire() {
+ if (isShutdown()) {
+ throw new IllegalStateException("TimedSemaphore is shut down!");
+ }
+
+ if (task == null) {
+ task = startTimer();
+ }
+ }
+
+ /**
+ * 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 a flag whether a permit could be acquired
+ */
+ private boolean acquirePermit() {
+ if (getLimit() <= NO_LIMIT || acquireCount < getLimit()) {
+ acquireCount++;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedExecutionException.java b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedExecutionException.java
new file mode 100644
index 000000000..9c721c2a7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedExecutionException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutionException;
+
+import org.apache.commons.lang3.exception.UncheckedException;
+
+/**
+ * Unchecked {@link ExecutionException}.
+ *
+ * @since 3.13.0
+ */
+public class UncheckedExecutionException extends UncheckedException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedExecutionException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFuture.java b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFuture.java
new file mode 100644
index 000000000..a8cc0b77b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFuture.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.concurrent;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.exception.UncheckedInterruptedException;
+
+/**
+ * An {@link Future} that throws unchecked instead checked exceptions.
+ *
+ * @param <V> The result type returned by this Future's {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ * @see Future
+ * @see Exception
+ * @since 3.13.0
+ */
+public interface UncheckedFuture<V> extends Future<V> {
+
+ /**
+ * Maps the given instances as unchecked.
+ *
+ * @param <T> The result type returned by the Futures' {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ *
+ * @param futures The Futures to uncheck.
+ * @return a new stream.
+ */
+ static <T> Stream<UncheckedFuture<T>> map(final Collection<Future<T>> futures) {
+ return futures.stream().map(UncheckedFuture::on);
+ }
+
+ /**
+ * Maps the given instances as unchecked.
+ *
+ * @param <T> The result type returned by the Futures' {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ *
+ * @param futures The Futures to uncheck.
+ * @return a new collection.
+ */
+ static <T> Collection<UncheckedFuture<T>> on(final Collection<Future<T>> futures) {
+ return map(futures).collect(Collectors.toList());
+ }
+
+ /**
+ * Creates a new instance on the given Future.
+ *
+ * @param <T> The result type returned by this Future's {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ *
+ * @param future The Future to uncheck.
+ * @return a new instance.
+ */
+ static <T> UncheckedFuture<T> on(final Future<T> future) {
+ return new UncheckedFutureImpl<>(future);
+ }
+
+ /**
+ * Gets per {@link Future#get()} but rethrows checked exceptions as unchecked.
+ * <p>
+ * The default mapping from checked to unchecked is:
+ * </p>
+ * <ul>
+ * <li>{@link InterruptedException} \u2192 {@link UncheckedInterruptedException}</li>
+ * <li>{@link ExecutionException} \u2192 {@link UncheckedExecutionException}</li>
+ * </ul>
+ */
+ @Override
+ V get();
+
+ /**
+ * Gets per {@link Future#get(long, TimeUnit)} but rethrows checked exceptions as unchecked.
+ * <p>
+ * The default mapping from checked to unchecked is:
+ * </p>
+ * <ul>
+ * <li>{@link InterruptedException} \u2192 {@link UncheckedInterruptedException}</li>
+ * <li>{@link ExecutionException} \u2192 {@link UncheckedExecutionException}</li>
+ * <li>{@link TimeoutException} \u2192 {@link UncheckedTimeoutException}</li>
+ * </ul>
+ */
+ @Override
+ V get(long timeout, TimeUnit unit);
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
new file mode 100644
index 000000000..bc826c9a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.lang3.exception.UncheckedInterruptedException;
+
+/**
+ * An {@link Future} implementation that throws unchecked instead of checked exceptions.
+ *
+ * @param <V> The result type returned by this UncheckedFuture's {@link #get()} and {@link #get(long, TimeUnit)} methods.
+ * @see Future
+ * @since 3.13.0
+ */
+class UncheckedFutureImpl<V> extends AbstractFutureProxy<V> implements UncheckedFuture<V> {
+
+ UncheckedFutureImpl(final Future<V> future) {
+ super(future);
+ }
+
+ @Override
+ public V get() {
+ try {
+ return super.get();
+ } catch (final InterruptedException e) {
+ throw new UncheckedInterruptedException(e);
+ } catch (final ExecutionException e) {
+ throw new UncheckedExecutionException(e);
+ }
+ }
+
+ @Override
+ public V get(final long timeout, final TimeUnit unit) {
+ try {
+ return super.get(timeout, unit);
+ } catch (final InterruptedException e) {
+ throw new UncheckedInterruptedException(e);
+ } catch (final ExecutionException e) {
+ throw new UncheckedExecutionException(e);
+ } catch (final TimeoutException e) {
+ throw new UncheckedTimeoutException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutException.java b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutException.java
new file mode 100644
index 000000000..254e4c3fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedTimeoutException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.lang3.exception.UncheckedException;
+
+/**
+ * Unchecked {@link TimeoutException}.
+ *
+ * @since 3.13.0
+ */
+public class UncheckedTimeoutException extends UncheckedException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedTimeoutException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
new file mode 100644
index 000000000..0d10ebe4c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent.locks;
+
+import java.util.Objects;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.StampedLock;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.function.Failable;
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableFunction;
+
+/**
+ * Combines the monitor and visitor pattern to work with {@link java.util.concurrent.locks.Lock locked objects}. Locked
+ * objects are an alternative to synchronization. This, on Wikipedia, is known as the Visitor pattern
+ * (https://en.wikipedia.org/wiki/Visitor_pattern), and from the "Gang of Four" "Design Patterns" book's Visitor pattern
+ * [Gamma, E., Helm, R., &amp; Johnson, R. (1998). Visitor. In Design patterns elements of reusable object oriented software (pp. 331-344). Reading: Addison Wesley.].
+ *
+ * <p>
+ * Locking is preferable, if there is a distinction between read access (multiple threads may have read access
+ * concurrently), and write access (only one thread may have write access at any given time). In comparison,
+ * synchronization doesn't support read access, because synchronized access is exclusive.
+ * </p>
+ * <p>
+ * Using this class is fairly straightforward:
+ * </p>
+ * <ol>
+ * <li>While still in single thread mode, create an instance of {@link LockingVisitors.StampedLockVisitor} by calling
+ * {@link #stampedLockVisitor(Object)}, passing the object which needs to be locked. Discard all references to the
+ * locked object. Instead, use references to the lock.</li>
+ * <li>If you want to access the locked object, create a {@link FailableConsumer}. The consumer will receive the locked
+ * object as a parameter. For convenience, the consumer may be implemented as a Lambda. Then invoke
+ * {@link LockingVisitors.StampedLockVisitor#acceptReadLocked(FailableConsumer)}, or
+ * {@link LockingVisitors.StampedLockVisitor#acceptWriteLocked(FailableConsumer)}, passing the consumer.</li>
+ * <li>As an alternative, if you need to produce a result object, you may use a {@link FailableFunction}. This function
+ * may also be implemented as a Lambda. To have the function executed, invoke
+ * {@link LockingVisitors.StampedLockVisitor#applyReadLocked(FailableFunction)}, or
+ * {@link LockingVisitors.StampedLockVisitor#applyWriteLocked(FailableFunction)}.</li>
+ * </ol>
+ * <p>
+ * Example: A thread safe logger class.
+ * </p>
+ *
+ * <pre>
+ * public class SimpleLogger {
+ *
+ * private final StampedLockVisitor&lt;PrintStream&gt; lock;
+ *
+ * public SimpleLogger(OutputStream out) {
+ * lock = LockingVisitors.stampedLockVisitor(new PrintStream(out));
+ * }
+ *
+ * public void log(String message) {
+ * lock.acceptWriteLocked((ps) -&gt; ps.println(message));
+ * }
+ *
+ * public void log(byte[] buffer) {
+ * lock.acceptWriteLocked((ps) -&gt; { ps.write(buffer); ps.println(); });
+ * }
+ * </pre>
+ *
+ * @since 3.11
+ */
+public class LockingVisitors {
+
+ /**
+ * Wraps a domain object and a lock for access by lambdas.
+ *
+ * @param <O> the wrapped object type.
+ * @param <L> the wrapped lock type.
+ */
+ public static class LockVisitor<O, L> {
+
+ /**
+ * The lock object, untyped, since, for example {@link StampedLock} does not implement a locking interface in
+ * Java 8.
+ */
+ private final L lock;
+
+ /**
+ * The guarded object.
+ */
+ private final O object;
+
+ /**
+ * Supplies the read lock, usually from the lock object.
+ */
+ private final Supplier<Lock> readLockSupplier;
+
+ /**
+ * Supplies the write lock, usually from the lock object.
+ */
+ private final Supplier<Lock> writeLockSupplier;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param object The object to guard.
+ * @param lock The locking object.
+ * @param readLockSupplier Supplies the read lock, usually from the lock object.
+ * @param writeLockSupplier Supplies the write lock, usually from the lock object.
+ */
+ protected LockVisitor(final O object, final L lock, final Supplier<Lock> readLockSupplier, final Supplier<Lock> writeLockSupplier) {
+ this.object = Objects.requireNonNull(object, "object");
+ this.lock = Objects.requireNonNull(lock, "lock");
+ this.readLockSupplier = Objects.requireNonNull(readLockSupplier, "readLockSupplier");
+ this.writeLockSupplier = Objects.requireNonNull(writeLockSupplier, "writeLockSupplier");
+ }
+
+ /**
+ * Provides read (shared, non-exclusive) access to the locked (hidden) object. More precisely, what the method
+ * will do (in the given order):
+ *
+ * <ol>
+ * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a
+ * lock is granted.</li>
+ * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li>
+ * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the
+ * lock will be released anyways.</li>
+ * </ol>
+ *
+ * @param consumer The consumer, which is being invoked to use the hidden object, which will be passed as the
+ * consumers parameter.
+ * @see #acceptWriteLocked(FailableConsumer)
+ * @see #applyReadLocked(FailableFunction)
+ */
+ public void acceptReadLocked(final FailableConsumer<O, ?> consumer) {
+ lockAcceptUnlock(readLockSupplier, consumer);
+ }
+
+ /**
+ * Provides write (exclusive) access to the locked (hidden) object. More precisely, what the method will do (in
+ * the given order):
+ *
+ * <ol>
+ * <li>Obtain a write (shared) lock on the locked (hidden) object. The current thread may block, until such a
+ * lock is granted.</li>
+ * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li>
+ * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the
+ * lock will be released anyways.</li>
+ * </ol>
+ *
+ * @param consumer The consumer, which is being invoked to use the hidden object, which will be passed as the
+ * consumers parameter.
+ * @see #acceptReadLocked(FailableConsumer)
+ * @see #applyWriteLocked(FailableFunction)
+ */
+ public void acceptWriteLocked(final FailableConsumer<O, ?> consumer) {
+ lockAcceptUnlock(writeLockSupplier, consumer);
+ }
+
+ /**
+ * Provides read (shared, non-exclusive) access to the locked (hidden) object for the purpose of computing a
+ * result object. More precisely, what the method will do (in the given order):
+ *
+ * <ol>
+ * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a
+ * lock is granted.</li>
+ * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter,
+ * receiving the functions result.</li>
+ * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the
+ * lock will be released anyways.</li>
+ * <li>Return the result object, that has been received from the functions invocation.</li>
+ * </ol>
+ * <p>
+ * <em>Example:</em> Consider that the hidden object is a list, and we wish to know the current size of the
+ * list. This might be achieved with the following:
+ * </p>
+ * <pre>
+ * private Lock&lt;List&lt;Object&gt;&gt; listLock;
+ *
+ * public int getCurrentListSize() {
+ * final Integer sizeInteger = listLock.applyReadLocked((list) -&gt; Integer.valueOf(list.size));
+ * return sizeInteger.intValue();
+ * }
+ * </pre>
+ *
+ * @param <T> The result type (both the functions, and this method's.)
+ * @param function The function, which is being invoked to compute the result. The function will receive the
+ * hidden object.
+ * @return The result object, which has been returned by the functions invocation.
+ * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend
+ * access to the hidden object beyond this methods lifetime and will therefore be prevented.
+ * @see #acceptReadLocked(FailableConsumer)
+ * @see #applyWriteLocked(FailableFunction)
+ */
+ public <T> T applyReadLocked(final FailableFunction<O, T, ?> function) {
+ return lockApplyUnlock(readLockSupplier, function);
+ }
+
+ /**
+ * Provides write (exclusive) access to the locked (hidden) object for the purpose of computing a result object.
+ * More precisely, what the method will do (in the given order):
+ *
+ * <ol>
+ * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a
+ * lock is granted.</li>
+ * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter,
+ * receiving the functions result.</li>
+ * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the
+ * lock will be released anyways.</li>
+ * <li>Return the result object, that has been received from the functions invocation.</li>
+ * </ol>
+ *
+ * @param <T> The result type (both the functions, and this method's.)
+ * @param function The function, which is being invoked to compute the result. The function will receive the
+ * hidden object.
+ * @return The result object, which has been returned by the functions invocation.
+ * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend
+ * access to the hidden object beyond this methods lifetime and will therefore be prevented.
+ * @see #acceptReadLocked(FailableConsumer)
+ * @see #applyWriteLocked(FailableFunction)
+ */
+ public <T> T applyWriteLocked(final FailableFunction<O, T, ?> function) {
+ return lockApplyUnlock(writeLockSupplier, function);
+ }
+
+ /**
+ * Gets the lock.
+ *
+ * @return the lock.
+ */
+ public L getLock() {
+ return lock;
+ }
+
+ /**
+ * Gets the guarded object.
+ *
+ * @return the object.
+ */
+ public O getObject() {
+ return object;
+ }
+
+ /**
+ * This method provides the default implementation for {@link #acceptReadLocked(FailableConsumer)}, and
+ * {@link #acceptWriteLocked(FailableConsumer)}.
+ *
+ * @param lockSupplier A supplier for the lock. (This provides, in fact, a long, because a {@link StampedLock} is used
+ * internally.)
+ * @param consumer The consumer, which is to be given access to the locked (hidden) object, which will be passed
+ * as a parameter.
+ * @see #acceptReadLocked(FailableConsumer)
+ * @see #acceptWriteLocked(FailableConsumer)
+ */
+ protected void lockAcceptUnlock(final Supplier<Lock> lockSupplier, final FailableConsumer<O, ?> consumer) {
+ final Lock lock = lockSupplier.get();
+ lock.lock();
+ try {
+ consumer.accept(object);
+ } catch (final Throwable t) {
+ throw Failable.rethrow(t);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * This method provides the actual implementation for {@link #applyReadLocked(FailableFunction)}, and
+ * {@link #applyWriteLocked(FailableFunction)}.
+ *
+ * @param <T> The result type (both the functions, and this method's.)
+ * @param lockSupplier A supplier for the lock. (This provides, in fact, a long, because a {@link StampedLock} is used
+ * internally.)
+ * @param function The function, which is being invoked to compute the result object. This function will receive
+ * the locked (hidden) object as a parameter.
+ * @return The result object, which has been returned by the functions invocation.
+ * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend
+ * access to the hidden object beyond this methods lifetime and will therefore be prevented.
+ * @see #applyReadLocked(FailableFunction)
+ * @see #applyWriteLocked(FailableFunction)
+ */
+ protected <T> T lockApplyUnlock(final Supplier<Lock> lockSupplier, final FailableFunction<O, T, ?> function) {
+ final Lock lock = lockSupplier.get();
+ lock.lock();
+ try {
+ return function.apply(object);
+ } catch (final Throwable t) {
+ throw Failable.rethrow(t);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ }
+
+ /**
+ * This class implements a wrapper for a locked (hidden) object, and provides the means to access it. The basic
+ * idea, is that the user code forsakes all references to the locked object, using only the wrapper object, and the
+ * accessor methods {@link #acceptReadLocked(FailableConsumer)}, {@link #acceptWriteLocked(FailableConsumer)},
+ * {@link #applyReadLocked(FailableFunction)}, and {@link #applyWriteLocked(FailableFunction)}. By doing so, the
+ * necessary protections are guaranteed.
+ *
+ * @param <O> The locked (hidden) objects type.
+ */
+ public static class ReadWriteLockVisitor<O> extends LockVisitor<O, ReadWriteLock> {
+
+ /**
+ * Creates a new instance with the given locked object. This constructor is supposed to be used for subclassing
+ * only. In general, it is suggested to use {@link LockingVisitors#stampedLockVisitor(Object)} instead.
+ *
+ * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object.
+ * @param readWriteLock the lock to use.
+ */
+ protected ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) {
+ super(object, readWriteLock, readWriteLock::readLock, readWriteLock::writeLock);
+ }
+ }
+
+ /**
+ * This class implements a wrapper for a locked (hidden) object, and provides the means to access it. The basic
+ * idea is that the user code forsakes all references to the locked object, using only the wrapper object, and the
+ * accessor methods {@link #acceptReadLocked(FailableConsumer)}, {@link #acceptWriteLocked(FailableConsumer)},
+ * {@link #applyReadLocked(FailableFunction)}, and {@link #applyWriteLocked(FailableFunction)}. By doing so, the
+ * necessary protections are guaranteed.
+ *
+ * @param <O> The locked (hidden) objects type.
+ */
+ public static class StampedLockVisitor<O> extends LockVisitor<O, StampedLock> {
+
+ /**
+ * Creates a new instance with the given locked object. This constructor is supposed to be used for subclassing
+ * only. In general, it is suggested to use {@link LockingVisitors#stampedLockVisitor(Object)} instead.
+ *
+ * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object.
+ * @param stampedLock the lock to use.
+ */
+ protected StampedLockVisitor(final O object, final StampedLock stampedLock) {
+ super(object, stampedLock, stampedLock::asReadLock, stampedLock::asWriteLock);
+ }
+ }
+
+ /**
+ * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object.
+ *
+ * @param <O> The locked objects type.
+ * @param object The locked (hidden) object.
+ * @return The created instance, a {@link StampedLockVisitor lock} for the given object.
+ */
+ public static <O> ReadWriteLockVisitor<O> reentrantReadWriteLockVisitor(final O object) {
+ return new LockingVisitors.ReadWriteLockVisitor<>(object, new ReentrantReadWriteLock());
+ }
+
+ /**
+ * Creates a new instance of {@link StampedLockVisitor} with the given (hidden) object.
+ *
+ * @param <O> The locked objects type.
+ * @param object The locked (hidden) object.
+ * @return The created instance, a {@link StampedLockVisitor lock} for the given object.
+ */
+ public static <O> StampedLockVisitor<O> stampedLockVisitor(final O object) {
+ return new LockingVisitors.StampedLockVisitor<>(object, new StampedLock());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java b/src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java
new file mode 100644
index 000000000..430e63404
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/locks/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides support classes for multi-threaded programming. This package is intended to be an extension to
+ * {@link java.util.concurrent.locks}.
+ *
+ * @since 3.11
+ */
+package org.apache.commons.lang3.concurrent.locks;
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/package-info.java b/src/main/java/org/apache/commons/lang3/concurrent/package-info.java
new file mode 100644
index 000000000..08d97f6d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/concurrent/package-info.java
@@ -0,0 +1,531 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides support classes for multi-threaded programming. This package is intended to be an extension to
+ * {@link java.util.concurrent}. These classes are thread-safe.
+ *
+ * <p>
+ * A group of classes deals with the correct creation and initialization of objects that are accessed by multiple
+ * threads. All these classes implement the {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} interface
+ * which provides just a single method:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public interface ConcurrentInitializer&lt;T&gt; {
+ * T get() throws ConcurrentException;
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * A {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} produces an object. By calling the
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer#get() get()} method the object managed by the
+ * initializer can be obtained. There are different implementations of the interface available addressing various use
+ * cases:
+ * </p>
+ *
+ * <p>
+ * {@link org.apache.commons.lang3.concurrent.ConstantInitializer} is a very straightforward implementation of the
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} interface: An instance is passed an object when it
+ * is constructed. In its {@code get()} method it simply returns this object. This is useful, for instance in unit tests
+ * or in cases when you want to pass a specific object to a component which expects a
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer}.
+ * </p>
+ *
+ * <p>
+ * The {@link org.apache.commons.lang3.concurrent.LazyInitializer} class can be used to defer the creation of an object
+ * until it is actually used. This makes sense, for instance, if the creation of the object is expensive and would slow
+ * down application startup or if the object is needed only for special executions.
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer} implements the <em>double-check idiom for an instance
+ * field</em> as discussed in Joshua Bloch's "Effective Java", 2nd edition, item 71. It uses <strong>volatile</strong>
+ * fields to reduce the amount of synchronization. Note that this idiom is appropriate for instance fields only. For
+ * <strong>static</strong> fields there are superior alternatives.
+ * </p>
+ *
+ * <p>
+ * We provide an example use case to demonstrate the usage of this class: A server application uses multiple worker
+ * threads to process client requests. If such a request causes a fatal error, an administrator is to be notified using
+ * a special messaging service. We assume that the creation of the messaging service is an expensive operation. So it
+ * should only be performed if an error actually occurs. Here is where
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer} comes into play. We create a specialized subclass for
+ * creating and initializing an instance of our messaging service.
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer} declares an abstract
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer#initialize() initialize()} method which we have to
+ * implement to create the messaging service object:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public class MessagingServiceInitializer extends LazyInitializer&lt;MessagingService&gt; {
+ * protected MessagingService initialize() throws ConcurrentException {
+ * // Do all necessary steps to create and initialize the service object
+ * MessagingService service = ...
+ * return service;
+ * }
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Now each server thread is passed a reference to a shared instance of our new {@code MessagingServiceInitializer}
+ * class. The threads run in a loop processing client requests. If an error is detected, the messaging service is
+ * obtained from the initializer, and the administrator is notified:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public class ServerThread implements Runnable {
+ * // The initializer for obtaining the messaging service.
+ * private final ConcurrentInitializer&lt;MessagingService&gt; initializer;
+ *
+ * public ServerThread(ConcurrentInitializer&lt;MessagingService&gt; init) {
+ * initializer = init;
+ * }
+ *
+ * public void run() {
+ * while (true) {
+ * try {
+ * // wait for request
+ * // process request
+ * } catch (FatalServerException ex) {
+ * // get messaging service
+ * try {
+ * MessagingService svc = initializer.get();
+ * svc.notifyAdministrator(ex);
+ * } catch (ConcurrentException cex) {
+ * cex.printStackTrace();
+ * }
+ * }
+ * }
+ * }
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * The {@link org.apache.commons.lang3.concurrent.AtomicInitializer} class is very similar to
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer}. It serves the same purpose: to defer the creation of an
+ * object until it is needed. The internal structure is also very similar. Again there is an abstract
+ * {@link org.apache.commons.lang3.concurrent.AtomicInitializer#initialize() initialize()} method which has to be
+ * implemented by concrete subclasses in order to create and initialize the managed object. Actually, in our example
+ * above we can turn the {@code MessagingServiceInitializer} into an atomic initializer by simply changing the
+ * <strong>extends</strong> declaration to refer to {@code AtomicInitializer&lt;MessagingService&gt;} as super class.
+ * </p>
+ *
+ * <p>
+ * With {@link org.apache.commons.lang3.concurrent.AtomicSafeInitializer} there is yet another variant implementing the
+ * lazy initializing pattern. Its implementation is close to
+ * {@link org.apache.commons.lang3.concurrent.AtomicInitializer}; it also uses atomic variables internally and therefore
+ * does not need synchronization. The name &quot;Safe&quot; is derived from the fact that it implements an additional
+ * check which guarantees that the {@link org.apache.commons.lang3.concurrent.AtomicSafeInitializer#initialize()
+ * initialize()} method is called only once. So it behaves exactly in the same way as
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer}.
+ * </p>
+ *
+ * <p>
+ * Now, which one of the lazy initializer implementations should you use? First of all we have to state that is
+ * problematic to give general recommendations regarding the performance of these classes. The initializers make use of
+ * low-level functionality whose efficiency depends on multiple factors including the target platform and the number of
+ * concurrent threads. So developers should make their own benchmarks in scenarios close to their specific use cases.
+ * The following statements are rules of thumb which have to be verified in practice.
+ * </p>
+ *
+ * <p>
+ * {@link org.apache.commons.lang3.concurrent.AtomicInitializer} is probably the most efficient implementation due to
+ * its lack of synchronization and further checks. Its main drawback is that the {@code initialize()} method can be
+ * called multiple times. In cases where this is not an issue
+ * {@link org.apache.commons.lang3.concurrent.AtomicInitializer} is a good choice.
+ * {@link org.apache.commons.lang3.concurrent.AtomicSafeInitializer} and
+ * {@link org.apache.commons.lang3.concurrent.LazyInitializer} both guarantee that the initialization method is called
+ * only once. Because {@link org.apache.commons.lang3.concurrent.AtomicSafeInitializer} does not use synchronization it
+ * is probably slightly more efficient than {@link org.apache.commons.lang3.concurrent.LazyInitializer}, but the
+ * concrete numbers might depend on the level of concurrency.
+ * </p>
+ *
+ * <p>
+ * Another implementation of the {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} interface is
+ * {@link org.apache.commons.lang3.concurrent.BackgroundInitializer}. It is again an abstract base class with an
+ * {@link org.apache.commons.lang3.concurrent.BackgroundInitializer#initialize() initialize()} method that has to be
+ * defined by concrete subclasses. The idea of {@link org.apache.commons.lang3.concurrent.BackgroundInitializer} is that
+ * it calls the {@code initialize()} method in a separate worker thread. An application creates a background initializer
+ * and starts it. Then it can continue with its work while the initializer runs in parallel. When the application needs
+ * the results of the initializer it calls its {@code get()} method. {@code get()} blocks until the initialization is
+ * complete. This is useful for instance at application startup. Here initialization steps (e.g. reading configuration
+ * files, opening a database connection, etc.) can be run in background threads while the application shows a splash
+ * screen and constructs its UI.
+ * </p>
+ *
+ * <p>
+ * As a concrete example consider an application that has to read the content of a URL - maybe a page with news - which
+ * is to be displayed to the user after login. Because loading the data over the network can take some time a
+ * specialized implementation of {@link org.apache.commons.lang3.concurrent.BackgroundInitializer} can be created for
+ * this purpose:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public class URLLoader extends BackgroundInitializer&lt;String&gt; {
+ * // The URL to be loaded.
+ * private final URL url;
+ *
+ * public URLLoader(URL u) {
+ * url = u;
+ * }
+ *
+ * protected String initialize() throws ConcurrentException {
+ * try {
+ * InputStream in = url.openStream();
+ * // read content into string
+ * ...
+ * return content;
+ * } catch (IOException ioex) {
+ * throw new ConcurrentException(ioex);
+ * }
+ * }
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * An application creates an instance of {@code URLLoader} and starts it. Then it can do other things. When it needs the
+ * content of the URL it calls the initializer's {@code get()} method:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * URL url = new URL("http://www.application-home-page.com/");
+ * URLLoader loader = new URLLoader(url);
+ * loader.start(); // this starts the background initialization
+ *
+ * // do other stuff
+ * ...
+ * // now obtain the content of the URL
+ * String content;
+ * try {
+ * content = loader.get(); // this may block
+ * } catch (ConcurrentException cex) {
+ * content = "Error when loading URL " + url;
+ * }
+ * // display content
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Related to {@link org.apache.commons.lang3.concurrent.BackgroundInitializer} is the
+ * {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} class. As the name implies, this class can
+ * handle multiple initializations in parallel. The basic usage scenario is that a
+ * {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} instance is created. Then an arbitrary number
+ * of {@link org.apache.commons.lang3.concurrent.BackgroundInitializer} objects is added using the
+ * {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer#addInitializer(String, BackgroundInitializer)}
+ * method. When adding an initializer a string has to be provided which is later used to obtain the result for this
+ * initializer. When all initializers have been added the
+ * {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer#start()} method is called. This starts
+ * processing of all initializers. Later the {@code get()} method can be called. It waits until all initializers have
+ * finished their initialization. {@code get()} returns an object of type
+ * {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer.MultiBackgroundInitializerResults}. This object
+ * provides information about all initializations that have been performed. It can be checked whether a specific
+ * initializer was successful or threw an exception. Of course, all initialization results can be queried.
+ * </p>
+ *
+ * <p>
+ * With {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} we can extend our example to perform
+ * multiple initialization steps. Suppose that in addition to loading a web site we also want to create a JPA entity
+ * manager factory and read a configuration file. We assume that corresponding
+ * {@link org.apache.commons.lang3.concurrent.BackgroundInitializer} implementations exist. The following example
+ * fragment shows the usage of {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} for this purpose:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * MultiBackgroundInitializer initializer = new MultiBackgroundInitializer();
+ * initializer.addInitializer("url", new URLLoader(url));
+ * initializer.addInitializer("jpa", new JPAEMFInitializer());
+ * initializer.addInitializer("config", new ConfigurationInitializer());
+ * initializer.start(); // start background processing
+ *
+ * // do other interesting things in parallel
+ * ...
+ * // evaluate the results of background initialization
+ * MultiBackgroundInitializer.MultiBackgroundInitializerResults results =
+ * initializer.get();
+ * String urlContent = (String) results.getResultObject("url");
+ * EntityManagerFactory emf =
+ * (EntityManagerFactory) results.getResultObject("jpa");
+ * ...
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * The child initializers are added to the multi initializer and are assigned a unique name. The object returned by the
+ * {@code get()} method is then queried for the single results using these unique names.
+ * </p>
+ *
+ * <p>
+ * If background initializers - including {@link org.apache.commons.lang3.concurrent.MultiBackgroundInitializer} - are
+ * created using the standard constructor, they create their own {@link java.util.concurrent.ExecutorService} which is
+ * used behind the scenes to execute the worker tasks. It is also possible to pass in an
+ * {@link java.util.concurrent.ExecutorService} when the initializer is constructed. That way client code can configure
+ * the {@link java.util.concurrent.ExecutorService} according to its specific needs; for instance, the number of threads
+ * available could be limited.
+ * </p>
+ *
+ * <h2>Utility Classes</h2>
+ *
+ * <p>
+ * Another group of classes in the new {@code concurrent} package offers some generic functionality related to
+ * concurrency. There is the {@link org.apache.commons.lang3.concurrent.ConcurrentUtils} class with a bunch of static
+ * utility methods. One focus of this class is dealing with exceptions thrown by JDK classes. Many JDK classes of the
+ * executor framework throw exceptions of type {@link java.util.concurrent.ExecutionException} if something goes wrong.
+ * The root cause of these exceptions can also be a runtime exception or even an error. In typical Java programming you
+ * often do not want to deal with runtime exceptions directly; rather you let them fall through the hierarchy of method
+ * invocations until they reach a central exception handler. Checked exceptions in contrast are usually handled close to
+ * their occurrence. With {@link java.util.concurrent.ExecutionException} this principle is violated. Because it is a
+ * checked exception, an application is forced to handle it even if the cause is a runtime exception. So you typically
+ * have to inspect the cause of the {@link java.util.concurrent.ExecutionException} and test whether it is a checked
+ * exception which has to be handled. If this is not the case, the causing exception can be rethrown.
+ * </p>
+ *
+ * <p>
+ * The {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#extractCause(java.util.concurrent.ExecutionException)}
+ * method does this work for you. It is passed an {@link java.util.concurrent.ExecutionException} and tests its root
+ * cause. If this is an error or a runtime exception, it is directly rethrown. Otherwise, an instance of
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentException} is created and initialized with the root cause
+ * ({@link org.apache.commons.lang3.concurrent.ConcurrentException} is a new exception class in the
+ * {@code o.a.c.l.concurrent} package). So if you get such a
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentException}, you can be sure that the original cause for the
+ * {@link java.util.concurrent.ExecutionException} was a checked exception. For users who prefer runtime exceptions in
+ * general there is also an
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#extractCauseUnchecked(java.util.concurrent.ExecutionException)}
+ * method which behaves like {@code extractCause()}, but returns the unchecked exception
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentRuntimeException} instead.
+ * </p>
+ *
+ * <p>
+ * In addition to the {@code extractCause()} methods there are corresponding
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#handleCause(java.util.concurrent.ExecutionException)} and
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#handleCauseUnchecked(java.util.concurrent.ExecutionException)}
+ * methods. These methods extract the cause of the passed in {@link java.util.concurrent.ExecutionException} and throw
+ * the resulting {@link org.apache.commons.lang3.concurrent.ConcurrentException} or
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentRuntimeException}. This makes it easy to transform an
+ * {@link java.util.concurrent.ExecutionException} into a
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentException} ignoring unchecked exceptions:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * Future&lt;Object&gt; future = ...;
+ * try {
+ * Object result = future.get();
+ * ...
+ * } catch (ExecutionException eex) {
+ * ConcurrentUtils.handleCause(eex);
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * There is also some support for the concurrent initializers introduced in the last sub section. The
+ * {@code initialize()} method is passed a {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} object and
+ * returns the object created by this initializer. It is null-safe. The {@code initializeUnchecked()} method works
+ * analogously, but a {@link org.apache.commons.lang3.concurrent.ConcurrentException} throws by the initializer is
+ * rethrown as a {@link org.apache.commons.lang3.concurrent.ConcurrentRuntimeException}. This is especially useful if
+ * the specific {@link org.apache.commons.lang3.concurrent.ConcurrentInitializer} does not throw checked exceptions.
+ * Using this method the code for requesting the object of an initializer becomes less verbose. The direct invocation
+ * looks as follows:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * ConcurrentInitializer&lt;MyClass&gt; initializer = ...;
+ * try {
+ * MyClass obj = initializer.get();
+ * // do something with obj
+ * } catch (ConcurrentException cex) {
+ * // exception handling
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Using the {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#initializeUnchecked(ConcurrentInitializer)}
+ * method, this becomes:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * ConcurrentInitializer&lt;MyClass&gt; initializer = ...;
+ * MyClass obj = ConcurrentUtils.initializeUnchecked(initializer);
+ * // do something with obj
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Another utility class deals with the creation of threads. When using the <em>Executor</em> framework new in JDK 1.5
+ * the developer usually does not have to care about creating threads; the executors create the threads they need on
+ * demand. However, sometimes it is desired to set some properties of the newly created worker threads. This is possible
+ * through the {@link java.util.concurrent.ThreadFactory} interface; an implementation of this interface has to be
+ * created and passed to an executor on creation time. Currently, the JDK does not provide an implementation of
+ * {@link java.util.concurrent.ThreadFactory}, so one has to start from scratch.
+ * </p>
+ *
+ * <p>
+ * With {@link org.apache.commons.lang3.concurrent.BasicThreadFactory} Commons Lang has an implementation of
+ * {@link java.util.concurrent.ThreadFactory} that works out of the box for many common use cases. For instance, it is
+ * possible to set a naming pattern for the new threads, set the daemon flag and a priority, or install a handler for
+ * uncaught exceptions. Instances of {@link org.apache.commons.lang3.concurrent.BasicThreadFactory} are created and
+ * configured using the nested {@link org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder} class. The
+ * following example shows a typical usage scenario:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * BasicThreadFactory factory = new BasicThreadFactory.Builder()
+ * .namingPattern("worker-thread-%d")
+ * .daemon(true)
+ * .uncaughtExceptionHandler(myHandler)
+ * .build();
+ * ExecutorService exec = Executors.newSingleThreadExecutor(factory);
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * The nested {@link org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder} class defines some methods for
+ * configuring the new {@link org.apache.commons.lang3.concurrent.BasicThreadFactory} instance. Objects of this class
+ * are immutable, so these attributes cannot be changed later. The naming pattern is a string which can be passed to
+ * {@link String#format(java.util.Locale, String, Object...)}. The placeholder <em>%d</em> is replaced by an
+ * increasing counter value. An instance can wrap another {@link java.util.concurrent.ThreadFactory} implementation;
+ * this is achieved by calling the builder's
+ * {@link org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder#wrappedFactory(java.util.concurrent.ThreadFactory)
+ * wrappedFactory(ThreadFactory)} method. This factory is then used for creating new threads; after that the specific
+ * attributes are applied to the new thread. If no wrapped factory is set, the default factory provided by the JDK is
+ * used.
+ * </p>
+ *
+ * <h2>Synchronization objects</h2>
+ *
+ * <p>
+ * The {@code concurrent} package also provides some support for specific synchronization problems with threads.
+ * </p>
+ *
+ * <p>
+ * {@link org.apache.commons.lang3.concurrent.TimedSemaphore} allows restricted access to a resource in a given time
+ * frame. Similar to a semaphore, a number of permits can be acquired. What is new is the fact that the permits
+ * available are related to a given time unit. For instance, the timed semaphore can be configured to allow 10 permits
+ * in a second. Now multiple threads access the semaphore and call its
+ * {@link org.apache.commons.lang3.concurrent.TimedSemaphore#acquire()} method. The semaphore keeps track about the
+ * number of granted permits in the current time frame. Only 10 calls are allowed; if there are further callers, they
+ * are blocked until the time frame (one second in this example) is over. Then all blocking threads are released, and
+ * the counter of available permits is reset to 0. So the game can start anew.
+ * </p>
+ *
+ * <p>
+ * What are use cases for {@link org.apache.commons.lang3.concurrent.TimedSemaphore}? One example is to artificially
+ * limit the load produced by multiple threads. Consider a batch application accessing a database to extract statistical
+ * data. The application runs multiple threads which issue database queries in parallel and perform some calculation on
+ * the results. If the database to be processed is huge and is also used by a production system, multiple factors have
+ * to be balanced: On one hand, the time required for the statistical evaluation should not take too long. Therefore you
+ * will probably use a larger number of threads because most of its life time a thread will just wait for the database
+ * to return query results. On the other hand, the load on the database generated by all these threads should be limited
+ * so that the responsiveness of the production system is not affected. With a
+ * {@link org.apache.commons.lang3.concurrent.TimedSemaphore} object this can be achieved. The semaphore can be
+ * configured to allow e.g. 100 queries per second. After these queries have been sent to the database the threads have
+ * to wait until the second is over - then they can query again. By fine-tuning the limit enforced by the semaphore a
+ * good balance between performance and database load can be established. It is even possible to chang? the number of
+ * available permits at runtime. So this number can be reduced during the typical working hours and increased at night.
+ * </p>
+ *
+ * <p>
+ * The following code examples demonstrate parts of the implementation of such a scenario. First the batch application
+ * has to create an instance of {@link org.apache.commons.lang3.concurrent.TimedSemaphore} and to initialize its
+ * properties with default values:
+ * </p>
+ *
+ * {@code TimedSemaphore semaphore = new TimedSemaphore(1, TimeUnit.SECONDS, 100);}
+ *
+ * <p>
+ * Here we specify that the semaphore should allow 100 permits in one second. This is effectively the limit of database
+ * queries per second in our example use case. Next the server threads issuing database queries and performing
+ * statistical operations can be initialized. They are passed a reference to the semaphore at creation time. Before they
+ * execute a query they have to acquire a permit.
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public class StatisticsTask implements Runnable {
+ * // The semaphore for limiting database load.
+ * private final TimedSemaphore semaphore;
+ *
+ * public StatisticsTask(TimedSemaphore sem, Connection con) {
+ * semaphore = sem;
+ * ...
+ * }
+ *
+ * //The main processing method. Executes queries and evaluates their results.
+ * public void run() {
+ * try {
+ * while (!isDone()) {
+ * semaphore.acquire(); // enforce the load limit
+ * executeAndEvaluateQuery();
+ * }
+ * } catch (InterruptedException iex) {
+ * // fall through
+ * }
+ * }
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * The important line here is the call to {@code semaphore.acquire()}. If the number of permits in the current time
+ * frame has not yet been reached, the call returns immediately. Otherwise, it blocks until the end of the time frame.
+ * The last piece missing is a scheduler service which adapts the number of permits allowed by the semaphore according
+ * to the time of day. We assume that this service is pretty simple and knows only two different time slots: working
+ * shift and night shift. The service is triggered periodically. It then determines the current time slot and configures
+ * the timed semaphore accordingly.
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * public class SchedulerService {
+ * // The semaphore for limiting database load.
+ * private final TimedSemaphore semaphore;
+ * ...
+ *
+ * // Configures the timed semaphore based on the current time of day. This method is called periodically.
+ * public void configureTimedSemaphore() {
+ * int limit;
+ * if (isWorkshift()) {
+ * limit = 50; // low database load
+ * } else {
+ * limit = 250; // high database load
+ * }
+ *
+ * semaphore.setLimit(limit);
+ * }
+ * }
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * With the {@link org.apache.commons.lang3.concurrent.TimedSemaphore#setLimit(int)} method the number of permits
+ * allowed for a time frame can be changed. There are some other methods for querying the internal state of a timed
+ * semaphore. Also some statistical data is available, e.g. the average number of {@code acquire()} calls per time
+ * frame. When a timed semaphore is no more needed, its {@code shutdown()} method has to be called.
+ * </p>
+ */
+package org.apache.commons.lang3.concurrent;
diff --git a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
new file mode 100644
index 000000000..9a934ef91
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.event;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * An EventListenerSupport object can be used to manage a list of event
+ * listeners of a particular type. The class provides
+ * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
+ * for registering listeners, as well as a {@link #fire()} method for firing
+ * events to the listeners.
+ *
+ * <p>
+ * To use this class, suppose you want to support ActionEvents. You would do:
+ * </p>
+ * <pre><code>
+ * public class MyActionEventSource
+ * {
+ * private EventListenerSupport&lt;ActionListener&gt; actionListeners =
+ * EventListenerSupport.create(ActionListener.class);
+ *
+ * public void someMethodThatFiresAction()
+ * {
+ * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
+ * actionListeners.fire().actionPerformed(e);
+ * }
+ * }
+ * </code></pre>
+ *
+ * <p>
+ * Serializing an {@link EventListenerSupport} instance will result in any
+ * non-{@link Serializable} listeners being silently dropped.
+ * </p>
+ *
+ * @param <L> the type of event listener that is supported by this proxy.
+ *
+ * @since 3.0
+ */
+public class EventListenerSupport<L> implements Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 3593265990380473632L;
+
+ /**
+ * The list used to hold the registered listeners. This list is
+ * intentionally a thread-safe copy-on-write-array so that traversals over
+ * the list of listeners will be atomic.
+ */
+ private List<L> listeners = new CopyOnWriteArrayList<>();
+
+ /**
+ * The proxy representing the collection of listeners. Calls to this proxy
+ * object will be sent to all registered listeners.
+ */
+ private transient L proxy;
+
+ /**
+ * Empty typed array for #getListeners().
+ */
+ private transient L[] prototypeArray;
+
+ /**
+ * Creates an EventListenerSupport object which supports the specified
+ * listener type.
+ *
+ * @param <T> 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 <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
+ return new EventListenerSupport<>(listenerInterface);
+ }
+
+ /**
+ * Creates an EventListenerSupport object which supports the provided
+ * listener interface.
+ *
+ * @param listenerInterface the type of listener interface that will receive
+ * events posted using this class.
+ *
+ * @throws NullPointerException if {@code listenerInterface} is
+ * {@code null}.
+ * @throws IllegalArgumentException if {@code listenerInterface} is
+ * not an interface.
+ */
+ public EventListenerSupport(final Class<L> listenerInterface) {
+ this(listenerInterface, Thread.currentThread().getContextClassLoader());
+ }
+
+ /**
+ * Creates an EventListenerSupport object which supports the provided
+ * listener interface using the specified class loader to create the JDK
+ * dynamic proxy.
+ *
+ * @param listenerInterface the listener interface.
+ * @param classLoader the class loader.
+ *
+ * @throws NullPointerException if {@code listenerInterface} or
+ * {@code classLoader} is {@code null}.
+ * @throws IllegalArgumentException if {@code listenerInterface} is
+ * not an interface.
+ */
+ public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) {
+ this();
+ Objects.requireNonNull(listenerInterface, "listenerInterface");
+ Objects.requireNonNull(classLoader, "classLoader");
+ Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface",
+ listenerInterface.getName());
+ 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
+//**********************************************************************************************************************
+
+ /**
+ * Registers an event listener.
+ *
+ * @param listener the event listener (may not be {@code null}).
+ *
+ * @throws NullPointerException if {@code listener} is
+ * {@code null}.
+ */
+ public void addListener(final L listener) {
+ addListener(listener, true);
+ }
+
+ /**
+ * Registers an event listener. Will not add a pre-existing listener
+ * object to the list if {@code allowDuplicate} is false.
+ *
+ * @param listener the event listener (may not be {@code null}).
+ * @param allowDuplicate the flag for determining if duplicate listener
+ * objects are allowed to be registered.
+ *
+ * @throws NullPointerException if {@code listener} is {@code null}.
+ * @since 3.5
+ */
+ public void addListener(final L listener, final boolean allowDuplicate) {
+ Objects.requireNonNull(listener, "listener");
+ if (allowDuplicate || !listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Returns the number of registered listeners.
+ *
+ * @return the number of registered listeners.
+ */
+ int getListenerCount() {
+ return listeners.size();
+ }
+
+ /**
+ * Unregisters an event listener.
+ *
+ * @param listener the event listener (may not be {@code null}).
+ *
+ * @throws NullPointerException if {@code listener} is
+ * {@code null}.
+ */
+ public void removeListener(final L listener) {
+ Objects.requireNonNull(listener, "listener");
+ 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
+ * @throws IOException if an IO error occurs
+ */
+ private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
+ final ArrayList<L> serializableListeners = new ArrayList<>();
+
+ // don't just rely on instanceof Serializable:
+ ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
+ for (final L listener : listeners) {
+ try {
+ testObjectOutputStream.writeObject(listener);
+ serializableListeners.add(listener);
+ } catch (final IOException exception) {
+ //recreate test stream in case of indeterminate state
+ testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
+ }
+ }
+ /*
+ * we can reconstitute everything we need from an array of our listeners,
+ * which has the additional advantage of typically requiring less storage than a list:
+ */
+ 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<L> 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<L> 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<L> 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
new file mode 100644
index 000000000..546c06818
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/event/EventUtils.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.event;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.reflect.MethodUtils;
+
+/**
+ * Provides some useful event-based utility methods.
+ *
+ * @since 3.0
+ */
+public class EventUtils {
+
+ /**
+ * Adds an event listener to the specified source. This looks for an "add" method corresponding to the event
+ * type (addActionListener, for example).
+ * @param eventSource the event source
+ * @param listenerType the event listener type
+ * @param listener the listener
+ * @param <L> the event listener type
+ *
+ * @throws IllegalArgumentException if the object doesn't support the listener type
+ */
+ public static <L> void addEventListener(final Object eventSource, final Class<L> listenerType, final L listener) {
+ try {
+ MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener);
+ } catch (final NoSuchMethodException e) {
+ throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
+ + " does not have a public add" + listenerType.getSimpleName()
+ + " method which takes a parameter of type " + listenerType.getName() + ".");
+ } catch (final IllegalAccessException e) {
+ throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
+ + " does not have an accessible add" + listenerType.getSimpleName ()
+ + " method which takes a parameter of type " + listenerType.getName() + ".");
+ } catch (final InvocationTargetException e) {
+ throw new IllegalArgumentException("Unable to add listener.", e.getCause());
+ }
+ }
+
+ /**
+ * Binds an event listener to a specific method on a specific object.
+ *
+ * @param <L> the event listener type
+ * @param target the target object
+ * @param methodName the name of the method to be called
+ * @param eventSource the object which is generating events (JButton, JList, etc.)
+ * @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.)
+ * @param eventTypes the event types (method names) from the listener interface (if none specified, all will be
+ * supported)
+ */
+ public static <L> void bindEventsToMethod(final Object target, final String methodName, final Object eventSource,
+ final Class<L> listenerType, final String... eventTypes) {
+ final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(),
+ new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes)));
+ addEventListener(eventSource, listenerType, listener);
+ }
+
+ private static class EventBindingInvocationHandler implements InvocationHandler {
+ private final Object target;
+ private final String methodName;
+ private final Set<String> 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/event/package-info.java b/src/main/java/org/apache/commons/lang3/event/package-info.java
new file mode 100644
index 000000000..0227a16bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/event/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides some useful event-based utilities.
+ *
+ * @since 3.0
+ */
+package org.apache.commons.lang3.event;
diff --git a/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java b/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java
new file mode 100644
index 000000000..7a4dcf26b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/CloneFailedException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+/**
+ * Exception thrown when a clone cannot be created. In contrast to
+ * {@link CloneNotSupportedException} this is a {@link RuntimeException}.
+ *
+ * @since 3.0
+ */
+public class CloneFailedException extends RuntimeException {
+
+ private static final long serialVersionUID = 20091223L;
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ */
+ public CloneFailedException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param cause cause of the exception
+ */
+ public CloneFailedException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a CloneFailedException.
+ *
+ * @param message description of the exception
+ * @param cause cause of the exception
+ */
+ public CloneFailedException(final String message, final Throwable cause) {
+ super(message, 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
new file mode 100644
index 000000000..b4c94537d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/ContextedException.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * An exception that provides an easy and safe way to add contextual information.
+ * <p>
+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
+ * Frequently what is needed is a select few pieces of local contextual data.
+ * Providing this data is tricky however, due to concerns over formatting and nulls.
+ * </p><p>
+ * The contexted exception approach allows the exception to be created together with a
+ * list of context label-value pairs. This additional information is automatically included in
+ * the message and printed stack trace.
+ * </p><p>
+ * An unchecked version of this exception is provided by ContextedRuntimeException.
+ * </p>
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (Exception e) {
+ * throw new ContextedException("Error posting account transaction", e)
+ * .addContextValue("Account Number", accountNumber)
+ * .addContextValue("Amount Posted", amountPosted)
+ * .addContextValue("Previous Balance", previousBalance);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * or improve diagnose data at a higher level:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (ContextedException e) {
+ * throw e.setContextValue("Transaction Id", transactionId);
+ * } catch (Exception e) {
+ * if (e instanceof ExceptionContext) {
+ * e.setContextValue("Transaction Id", transactionId);
+ * }
+ * throw e;
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The output in a printStacktrace() (which often is written to a log) would look something like the following:
+ * </p>
+ * <pre>
+ * org.apache.commons.lang3.exception.ContextedException: java.lang.Exception: Error posting account transaction
+ * Exception Context:
+ * [1:Account Number=null]
+ * [2:Amount Posted=100.00]
+ * [3:Previous Balance=-2.17]
+ * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ * ---------------------------------
+ * at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ * ..... (rest of trace)
+ * </pre>
+ *
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public class ContextedException extends Exception implements ExceptionContext {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+ /** The context where the data is stored. */
+ private final ExceptionContext exceptionContext;
+
+ /**
+ * Instantiates ContextedException without message or cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ */
+ public ContextedException() {
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with message, but without cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ */
+ public ContextedException(final String message) {
+ super(message);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause, but without message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedException(final Throwable cause) {
+ super(cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause and message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedException(final String message, final Throwable cause) {
+ super(message, cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedException with cause, message, and ExceptionContext.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ * @param context the context used to store the additional information, null uses default implementation
+ */
+ public ContextedException(final String message, final Throwable cause, ExceptionContext context) {
+ super(message, cause);
+ if (context == null) {
+ context = new DefaultExceptionContext();
+ }
+ exceptionContext = context;
+ }
+
+ /**
+ * Adds information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Different values can be added with the same label multiple times.
+ * <p>
+ * Note: This exception is only serializable if the object added is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ @Override
+ public ContextedException addContextValue(final String label, final Object value) {
+ exceptionContext.addContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * Sets information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Any existing values with the same labels are removed before the new one is added.
+ * <p>
+ * Note: This exception is only serializable if the object added as value is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ @Override
+ public ContextedException setContextValue(final String label, final Object value) {
+ exceptionContext.setContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Object> getContextValues(final String label) {
+ return this.exceptionContext.getContextValues(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getFirstContextValue(final String label) {
+ return this.exceptionContext.getFirstContextValue(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Pair<String, Object>> getContextEntries() {
+ return this.exceptionContext.getContextEntries();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getContextLabels() {
+ return exceptionContext.getContextLabels();
+ }
+
+ /**
+ * Provides the message explaining the exception, including the contextual data.
+ *
+ * @see Throwable#getMessage()
+ * @return the message, never null
+ */
+ @Override
+ public String getMessage() {
+ return getFormattedExceptionMessage(super.getMessage());
+ }
+
+ /**
+ * Provides the message explaining the exception without the contextual data.
+ *
+ * @see Throwable#getMessage()
+ * @return the message
+ * @since 3.0.1
+ */
+ public String getRawMessage() {
+ return super.getMessage();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getFormattedExceptionMessage(final String baseMessage) {
+ return exceptionContext.getFormattedExceptionMessage(baseMessage);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java b/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java
new file mode 100644
index 000000000..59e4e5990
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/ContextedRuntimeException.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * A runtime exception that provides an easy and safe way to add contextual information.
+ * <p>
+ * An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
+ * Frequently what is needed is a select few pieces of local contextual data.
+ * Providing this data is tricky however, due to concerns over formatting and nulls.
+ * </p><p>
+ * The contexted exception approach allows the exception to be created together with a
+ * list of context label-value pairs. This additional information is automatically included in
+ * the message and printed stack trace.
+ * </p><p>
+ * A checked version of this exception is provided by ContextedException.
+ * </p>
+ * <p>
+ * To use this class write code as follows:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (Exception e) {
+ * throw new ContextedRuntimeException("Error posting account transaction", e)
+ * .addContextValue("Account Number", accountNumber)
+ * .addContextValue("Amount Posted", amountPosted)
+ * .addContextValue("Previous Balance", previousBalance);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * or improve diagnose data at a higher level:
+ * </p>
+ * <pre>
+ * try {
+ * ...
+ * } catch (ContextedRuntimeException e) {
+ * throw e.setContextValue("Transaction Id", transactionId);
+ * } catch (Exception e) {
+ * if (e instanceof ExceptionContext) {
+ * e.setContextValue("Transaction Id", transactionId);
+ * }
+ * throw e;
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The output in a printStacktrace() (which often is written to a log) would look something like the following:
+ * </p>
+ * <pre>
+ * org.apache.commons.lang3.exception.ContextedRuntimeException: java.lang.Exception: Error posting account transaction
+ * Exception Context:
+ * [1:Account Number=null]
+ * [2:Amount Posted=100.00]
+ * [3:Previous Balance=-2.17]
+ * [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
+ *
+ * ---------------------------------
+ * at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
+ * ..... (rest of trace)
+ * </pre>
+ *
+ * @see ContextedException
+ * @since 3.0
+ */
+public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+ /** The context where the data is stored. */
+ private final ExceptionContext exceptionContext;
+
+ /**
+ * Instantiates ContextedRuntimeException without message or cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ */
+ public ContextedRuntimeException() {
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with message, but without cause.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ */
+ public ContextedRuntimeException(final String message) {
+ super(message);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause, but without message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedRuntimeException(final Throwable cause) {
+ super(cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause and message.
+ * <p>
+ * The context information is stored using a default implementation.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ */
+ public ContextedRuntimeException(final String message, final Throwable cause) {
+ super(message, cause);
+ exceptionContext = new DefaultExceptionContext();
+ }
+
+ /**
+ * Instantiates ContextedRuntimeException with cause, message, and ExceptionContext.
+ *
+ * @param message the exception message, may be null
+ * @param cause the underlying cause of the exception, may be null
+ * @param context the context used to store the additional information, null uses default implementation
+ */
+ public ContextedRuntimeException(final String message, final Throwable cause, ExceptionContext context) {
+ super(message, cause);
+ if (context == null) {
+ context = new DefaultExceptionContext();
+ }
+ exceptionContext = context;
+ }
+
+ /**
+ * Adds information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Different values can be added with the same label multiple times.
+ * <p>
+ * Note: This exception is only serializable if the object added is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ @Override
+ public ContextedRuntimeException addContextValue(final String label, final Object value) {
+ exceptionContext.addContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * Sets information helpful to a developer in diagnosing and correcting the problem.
+ * For the information to be meaningful, the value passed should have a reasonable
+ * toString() implementation.
+ * Any existing values with the same labels are removed before the new one is added.
+ * <p>
+ * Note: This exception is only serializable if the object added as value is serializable.
+ * </p>
+ *
+ * @param label a textual label associated with information, {@code null} not recommended
+ * @param value information needed to understand exception, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ @Override
+ public ContextedRuntimeException setContextValue(final String label, final Object value) {
+ exceptionContext.setContextValue(label, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Object> getContextValues(final String label) {
+ return this.exceptionContext.getContextValues(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getFirstContextValue(final String label) {
+ return this.exceptionContext.getFirstContextValue(label);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Pair<String, Object>> getContextEntries() {
+ return this.exceptionContext.getContextEntries();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getContextLabels() {
+ return exceptionContext.getContextLabels();
+ }
+
+ /**
+ * Provides the message explaining the exception, including the contextual data.
+ *
+ * @see Throwable#getMessage()
+ * @return the message, never null
+ */
+ @Override
+ public String getMessage() {
+ return getFormattedExceptionMessage(super.getMessage());
+ }
+
+ /**
+ * Provides the message explaining the exception without the contextual data.
+ *
+ * @see Throwable#getMessage()
+ * @return the message
+ * @since 3.0.1
+ */
+ public String getRawMessage() {
+ return super.getMessage();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getFormattedExceptionMessage(final String baseMessage) {
+ return exceptionContext.getFormattedExceptionMessage(baseMessage);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java
new file mode 100644
index 000000000..51bde8b50
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Default implementation of the context storing the label-value pairs for contexted exceptions.
+ * <p>
+ * This implementation is serializable, however this is dependent on the values that
+ * are added also being serializable.
+ * </p>
+ *
+ * @see ContextedException
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public class DefaultExceptionContext implements ExceptionContext, Serializable {
+
+ /** The serialization version. */
+ private static final long serialVersionUID = 20110706L;
+
+ /** The list storing the label-data pairs. */
+ private final List<Pair<String, Object>> contextValues = new ArrayList<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DefaultExceptionContext addContextValue(final String label, final Object value) {
+ contextValues.add(new ImmutablePair<>(label, value));
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Pair<String, Object>> getContextEntries() {
+ return contextValues;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getContextLabels() {
+ return stream().map(Pair::getKey).collect(Collectors.toSet());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Object> getContextValues(final String label) {
+ return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).map(Pair::getValue).collect(Collectors.toList());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getFirstContextValue(final String label) {
+ return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).findFirst().map(Pair::getValue).orElse(null);
+ }
+
+ /**
+ * Builds the message containing the contextual information.
+ *
+ * @param baseMessage the base exception message <b>without</b> context information appended
+ * @return the exception message <b>with</b> context information appended, never null
+ */
+ @Override
+ public String getFormattedExceptionMessage(final String baseMessage) {
+ final StringBuilder buffer = new StringBuilder(256);
+ if (baseMessage != null) {
+ buffer.append(baseMessage);
+ }
+
+ if (!contextValues.isEmpty()) {
+ if (buffer.length() > 0) {
+ buffer.append('\n');
+ }
+ buffer.append("Exception Context:\n");
+
+ int i = 0;
+ for (final Pair<String, Object> pair : contextValues) {
+ buffer.append("\t[");
+ buffer.append(++i);
+ buffer.append(':');
+ buffer.append(pair.getKey());
+ buffer.append("=");
+ final Object value = pair.getValue();
+ if (value == null) {
+ buffer.append("null");
+ } else {
+ String valueStr;
+ try {
+ valueStr = value.toString();
+ } catch (final Exception e) {
+ valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e);
+ }
+ buffer.append(valueStr);
+ }
+ buffer.append("]\n");
+ }
+ buffer.append("---------------------------------");
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DefaultExceptionContext setContextValue(final String label, final Object value) {
+ contextValues.removeIf(p -> StringUtils.equals(label, p.getKey()));
+ addContextValue(label, value);
+ return this;
+ }
+
+ private Stream<Pair<String, Object>> stream() {
+ return contextValues.stream();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java b/src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java
new file mode 100644
index 000000000..121f8f466
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionContext.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Allows the storage and retrieval of contextual information based on label-value
+ * pairs for exceptions.
+ * <p>
+ * Implementations are expected to manage the pairs in a list-style collection
+ * that keeps the pairs in the sequence of their addition.
+ * </p>
+ *
+ * @see ContextedException
+ * @see ContextedRuntimeException
+ * @since 3.0
+ */
+public interface ExceptionContext {
+
+ /**
+ * Adds a contextual label-value pair into this context.
+ * <p>
+ * The pair will be added to the context, independently of an already
+ * existing pair with the same label.
+ * </p>
+ *
+ * @param label the label of the item to add, {@code null} not recommended
+ * @param value the value of item to add, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ ExceptionContext addContextValue(String label, Object value);
+
+ /**
+ * Retrieves the full list of label-value pairs defined in the contextual data.
+ *
+ * @return the list of pairs, not {@code null}
+ */
+ List<Pair<String, Object>> getContextEntries();
+
+ /**
+ * Retrieves the full set of labels defined in the contextual data.
+ *
+ * @return the set of labels, not {@code null}
+ */
+ Set<String> getContextLabels();
+
+ /**
+ * Retrieves all the contextual data values associated with the label.
+ *
+ * @param label the label to get the contextual values for, may be {@code null}
+ * @return the contextual values associated with the label, never {@code null}
+ */
+ List<Object> getContextValues(String label);
+
+ /**
+ * Retrieves the first available contextual data value associated with the label.
+ *
+ * @param label the label to get the contextual value for, may be {@code null}
+ * @return the first contextual value associated with the label, may be {@code null}
+ */
+ Object getFirstContextValue(String label);
+
+ /**
+ * Gets the contextualized error message based on a base message.
+ * This will add the context label-value pairs to the message.
+ *
+ * @param baseMessage the base exception message <b>without</b> context information appended
+ * @return the exception message <b>with</b> context information appended, not {@code null}
+ */
+ String getFormattedExceptionMessage(String baseMessage);
+
+ /**
+ * Sets a contextual label-value pair into this context.
+ * <p>
+ * The pair will be added normally, but any existing label-value pair with
+ * the same label is removed from the context.
+ * </p>
+ *
+ * @param label the label of the item to add, {@code null} not recommended
+ * @param value the value of item to add, may be {@code null}
+ * @return {@code this}, for method chaining, not {@code null}
+ */
+ ExceptionContext setContextValue(String label, Object value);
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
new file mode 100644
index 000000000..08f038af9
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
@@ -0,0 +1,981 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Provides utilities for manipulating and examining
+ * {@link Throwable} objects.
+ *
+ * @since 1.0
+ */
+public class ExceptionUtils {
+
+ private static final int NOT_FOUND = -1;
+
+ /**
+ * The names of methods commonly used to access a wrapped exception.
+ */
+ // TODO: Remove in Lang 4.0
+ private static final String[] CAUSE_METHOD_NAMES = {
+ "getCause",
+ "getNextException",
+ "getTargetException",
+ "getException",
+ "getSourceException",
+ "getRootCause",
+ "getCausedByException",
+ "getNested",
+ "getLinkedException",
+ "getNestedException",
+ "getLinkedCause",
+ "getThrowable",
+ };
+
+ /**
+ * Used when printing stack frames to denote the start of a
+ * wrapped exception.
+ *
+ * <p>Package private for accessibility by test suite.</p>
+ */
+ static final String WRAPPED_MARKER = " [wrapped] ";
+
+ /**
+ * Claims a Throwable is another Throwable type using type erasure. This
+ * hides a checked exception from the Java compiler, allowing a checked
+ * exception to be thrown without having the exception in the method's throw
+ * clause.
+ */
+ @SuppressWarnings("unchecked")
+ private static <R, T extends Throwable> R eraseType(final Throwable throwable) throws T {
+ throw (T) throwable;
+ }
+
+ /**
+ * Introspects the {@link Throwable} to obtain the cause.
+ *
+ * <p>The method searches for methods with specific names that return a
+ * {@link Throwable} object. This will pick up most wrapping exceptions,
+ * including those from JDK 1.4.
+ * </p>
+ *
+ * <p>The default list searched for are:</p>
+ * <ul>
+ * <li>{@code getCause()}</li>
+ * <li>{@code getNextException()}</li>
+ * <li>{@code getTargetException()}</li>
+ * <li>{@code getException()}</li>
+ * <li>{@code getSourceException()}</li>
+ * <li>{@code getRootCause()}</li>
+ * <li>{@code getCausedByException()}</li>
+ * <li>{@code getNested()}</li>
+ * </ul>
+ *
+ * <p>If none of the above is found, returns {@code null}.</p>
+ *
+ * @param throwable the throwable to introspect for a cause, may be null
+ * @return the cause of the {@link Throwable},
+ * {@code null} if none found or null throwable input
+ * @since 1.0
+ * @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
+ */
+ @Deprecated
+ public static Throwable getCause(final Throwable throwable) {
+ return getCause(throwable, null);
+ }
+
+ /**
+ * Introspects the {@link Throwable} to obtain the cause.
+ *
+ * <p>A {@code null} set of method names means use the default set.
+ * A {@code null} in the set of method names will be ignored.</p>
+ *
+ * @param throwable the throwable to introspect for a cause, may be null
+ * @param methodNames the method names, null treated as default set
+ * @return the cause of the {@link Throwable},
+ * {@code null} if none found or null throwable input
+ * @since 1.0
+ * @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
+ */
+ @Deprecated
+ public static Throwable getCause(final Throwable throwable, String[] methodNames) {
+ if (throwable == null) {
+ return null;
+ }
+ if (methodNames == null) {
+ final Throwable cause = throwable.getCause();
+ if (cause != null) {
+ return cause;
+ }
+ methodNames = CAUSE_METHOD_NAMES;
+ }
+ return Stream.of(methodNames).map(m -> getCauseUsingMethodName(throwable, m)).filter(Objects::nonNull).findFirst().orElse(null);
+ }
+
+ /**
+ * Gets a {@link Throwable} by method name.
+ *
+ * @param throwable the exception to examine
+ * @param methodName the name of the method to find and invoke
+ * @return the wrapped exception, or {@code null} if not found
+ */
+ // TODO: Remove in Lang 4.0
+ private static Throwable getCauseUsingMethodName(final Throwable throwable, final String methodName) {
+ if (methodName != null) {
+ Method method = null;
+ try {
+ method = throwable.getClass().getMethod(methodName);
+ } catch (final NoSuchMethodException | SecurityException ignored) {
+ // exception ignored
+ }
+
+ if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
+ try {
+ return (Throwable) method.invoke(throwable);
+ } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) {
+ // exception ignored
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the default names used when searching for the cause of an exception.
+ *
+ * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
+ *
+ * @return cloned array of the default method names
+ * @since 3.0
+ * @deprecated This feature will be removed in Lang 4.0
+ */
+ @Deprecated
+ public static String[] getDefaultCauseMethodNames() {
+ return ArrayUtils.clone(CAUSE_METHOD_NAMES);
+ }
+
+ /**
+ * Gets a short message summarizing the exception.
+ * <p>
+ * The message returned is of the form
+ * {ClassNameWithoutPackage}: {ThrowableMessage}
+ * </p>
+ *
+ * @param th the throwable to get a message for, null returns empty string
+ * @return the message, non-null
+ * @since 2.2
+ */
+ public static String getMessage(final Throwable th) {
+ if (th == null) {
+ return StringUtils.EMPTY;
+ }
+ final String clsName = ClassUtils.getShortClassName(th, null);
+ return clsName + ": " + StringUtils.defaultString(th.getMessage());
+ }
+
+ /**
+ * Introspects the {@link Throwable} to obtain the root cause.
+ *
+ * <p>This method walks through the exception chain to the last element,
+ * "root" of the tree, using {@link Throwable#getCause()}, and
+ * returns that exception.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. If the throwable parameter
+ * has a cause of itself, then null will be returned. If the throwable
+ * parameter cause chain loops, the last element in the chain before the
+ * loop is returned.</p>
+ *
+ * @param throwable the throwable to get the root cause for, may be null
+ * @return the root cause of the {@link Throwable},
+ * {@code null} if null throwable input
+ */
+ public static Throwable getRootCause(final Throwable throwable) {
+ final List<Throwable> list = getThrowableList(throwable);
+ return list.isEmpty() ? null : list.get(list.size() - 1);
+ }
+
+ /**
+ * Gets a short message summarizing the root cause exception.
+ * <p>
+ * The message returned is of the form
+ * {ClassNameWithoutPackage}: {ThrowableMessage}
+ * </p>
+ *
+ * @param throwable the throwable to get a message for, null returns empty string
+ * @return the message, non-null
+ * @since 2.2
+ */
+ public static String getRootCauseMessage(final Throwable throwable) {
+ final Throwable root = getRootCause(throwable);
+ return getMessage(root == null ? throwable : root);
+ }
+
+ /**
+ * Gets a compact stack trace for the root cause of the supplied
+ * {@link Throwable}.
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * It consists of the root exception followed by each of its wrapping
+ * exceptions separated by '[wrapped]'. Note that this is the opposite
+ * order to the JDK1.4 display.</p>
+ *
+ * @param throwable the throwable to examine, may be null
+ * @return an array of stack trace frames, never null
+ * @since 2.0
+ */
+ public static String[] getRootCauseStackTrace(final Throwable throwable) {
+ return getRootCauseStackTraceList(throwable).toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Gets a compact stack trace for the root cause of the supplied {@link Throwable}.
+ *
+ * <p>
+ * The output of this method is consistent across JDK versions. It consists of the root exception followed by each of
+ * its wrapping exceptions separated by '[wrapped]'. Note that this is the opposite order to the JDK1.4 display.
+ * </p>
+ *
+ * @param throwable the throwable to examine, may be null
+ * @return a list of stack trace frames, never null
+ * @since 3.13.0
+ */
+ public static List<String> getRootCauseStackTraceList(final Throwable throwable) {
+ if (throwable == null) {
+ return Collections.emptyList();
+ }
+ final Throwable[] throwables = getThrowables(throwable);
+ final int count = throwables.length;
+ final List<String> frames = new ArrayList<>();
+ List<String> nextTrace = getStackFrameList(throwables[count - 1]);
+ for (int i = count; --i >= 0;) {
+ final List<String> trace = nextTrace;
+ if (i != 0) {
+ nextTrace = getStackFrameList(throwables[i - 1]);
+ removeCommonFrames(trace, nextTrace);
+ }
+ if (i == count - 1) {
+ frames.add(throwables[i].toString());
+ } else {
+ frames.add(WRAPPED_MARKER + throwables[i].toString());
+ }
+ frames.addAll(trace);
+ }
+ return frames;
+ }
+
+ /**
+ * Gets a {@link List} of stack frames - the message
+ * is not included. Only the trace of the specified exception is
+ * returned, any caused by trace is stripped.
+ *
+ * <p>This works in most cases - it will only fail if the exception
+ * message contains a line that starts with:
+ * {@code &quot;&nbsp;&nbsp;&nbsp;at&quot;.}</p>
+ *
+ * @param throwable is any throwable
+ * @return List of stack frames
+ */
+ static List<String> getStackFrameList(final Throwable throwable) {
+ final String stackTrace = getStackTrace(throwable);
+ final String linebreak = System.lineSeparator();
+ final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ final List<String> list = new ArrayList<>();
+ boolean traceStarted = false;
+ while (frames.hasMoreTokens()) {
+ final String token = frames.nextToken();
+ // Determine if the line starts with <whitespace>at
+ final int at = token.indexOf("at");
+ if (at != NOT_FOUND && token.substring(0, at).trim().isEmpty()) {
+ traceStarted = true;
+ list.add(token);
+ } else if (traceStarted) {
+ break;
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Gets an array where each element is a line from the argument.
+ *
+ * <p>The end of line is determined by the value of {@link System#lineSeparator()}.</p>
+ *
+ * @param stackTrace a stack trace String
+ * @return an array where each element is a line from the argument
+ */
+ static String[] getStackFrames(final String stackTrace) {
+ final String linebreak = System.lineSeparator();
+ final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ final List<String> list = new ArrayList<>();
+ while (frames.hasMoreTokens()) {
+ list.add(frames.nextToken());
+ }
+ return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Gets the stack trace associated with the specified
+ * {@link Throwable} object, decomposing it into a list of
+ * stack frames.
+ *
+ * <p>The result of this method vary by JDK version as this method
+ * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+ * On JDK1.3 and earlier, the cause exception will not be shown
+ * unless the specified throwable alters printStackTrace.</p>
+ *
+ * @param throwable the {@link Throwable} to examine, may be null
+ * @return an array of strings describing each stack frame, never null
+ */
+ public static String[] getStackFrames(final Throwable throwable) {
+ if (throwable == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ return getStackFrames(getStackTrace(throwable));
+ }
+
+ /**
+ * Gets the stack trace from a Throwable as a String.
+ *
+ * <p>The result of this method vary by JDK version as this method
+ * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+ * On JDK1.3 and earlier, the cause exception will not be shown
+ * unless the specified throwable alters printStackTrace.</p>
+ *
+ * @param throwable the {@link Throwable} to be examined, may be null
+ * @return the stack trace as generated by the exception's
+ * {@code printStackTrace(PrintWriter)} method, or an empty String if {@code null} input
+ */
+ public static String getStackTrace(final Throwable throwable) {
+ if (throwable == null) {
+ return StringUtils.EMPTY;
+ }
+ final StringWriter sw = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(sw, true));
+ return sw.toString();
+ }
+
+ /**
+ * Gets a count of the number of {@link Throwable} objects in the
+ * exception chain.
+ *
+ * <p>A throwable without cause will return {@code 1}.
+ * A throwable with one cause will return {@code 2} and so on.
+ * A {@code null} throwable will return {@code 0}.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the
+ * chain is already in the result set.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @return the count of throwables, zero if null input
+ */
+ public static int getThrowableCount(final Throwable throwable) {
+ return getThrowableList(throwable).size();
+ }
+
+ /**
+ * Gets the list of {@link Throwable} objects in the
+ * exception chain.
+ *
+ * <p>A throwable without cause will return a list containing
+ * one element - the input throwable.
+ * A throwable with one cause will return a list containing
+ * two elements. - the input throwable and the cause throwable.
+ * A {@code null} throwable will return a list of size zero.</p>
+ *
+ * <p>This method handles recursive cause structures that might
+ * otherwise cause infinite loops. The cause chain is processed until
+ * the end is reached, or until the next item in the chain is already
+ * in the result set.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @return the list of throwables, never null
+ * @since 2.2
+ */
+ public static List<Throwable> getThrowableList(Throwable throwable) {
+ final List<Throwable> list = new ArrayList<>();
+ while (throwable != null && !list.contains(throwable)) {
+ list.add(throwable);
+ throwable = throwable.getCause();
+ }
+ return list;
+ }
+
+ /**
+ * Performs an action for each Throwable causes of the given Throwable.
+ * <p>
+ * A throwable without cause will return a stream containing one element - the input throwable. A throwable with one cause
+ * will return a stream containing two elements. - the input throwable and the cause throwable. A {@code null} throwable
+ * will return a stream of count zero.
+ * </p>
+ *
+ * <p>
+ * This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the chain is already in the result set.
+ * </p>
+ * @param throwable The Throwable to traverse.
+ * @param consumer a non-interfering action to perform on the elements.
+ * @since 3.13.0
+ */
+ public static void forEach(final Throwable throwable, final Consumer<Throwable> consumer) {
+ stream(throwable).forEach(consumer);
+ }
+
+ /**
+ * Gets the list of {@link Throwable} objects in the
+ * exception chain.
+ *
+ * <p>A throwable without cause will return an array containing
+ * one element - the input throwable.
+ * A throwable with one cause will return an array containing
+ * two elements. - the input throwable and the cause throwable.
+ * A {@code null} throwable will return an array of size zero.</p>
+ *
+ * <p>From version 2.2, this method handles recursive cause structures
+ * that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the
+ * chain is already in the result set.</p>
+ *
+ * @see #getThrowableList(Throwable)
+ * @param throwable the throwable to inspect, may be null
+ * @return the array of throwables, never null
+ */
+ public static Throwable[] getThrowables(final Throwable throwable) {
+ return getThrowableList(throwable).toArray(ArrayUtils.EMPTY_THROWABLE_ARRAY);
+ }
+
+ /**
+ * Tests if the throwable's causal chain have an immediate or wrapped exception
+ * of the given type?
+ *
+ * @param chain
+ * The root of a Throwable causal chain.
+ * @param type
+ * The exception type to test.
+ * @return true, if chain is an instance of type or is an
+ * UndeclaredThrowableException wrapping a cause.
+ * @since 3.5
+ * @see #wrapAndThrow(Throwable)
+ */
+ public static boolean hasCause(Throwable chain,
+ final Class<? extends Throwable> type) {
+ if (chain instanceof UndeclaredThrowableException) {
+ chain = chain.getCause();
+ }
+ return type.isInstance(chain);
+ }
+
+ /**
+ * Worker method for the {@code indexOfType} methods.
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @param subclass if {@code true}, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
+ * using references
+ * @return index of the {@code type} within throwables nested within the specified {@code throwable}
+ */
+ private static int indexOf(final Throwable throwable, final Class<? extends Throwable> type, int fromIndex, final boolean subclass) {
+ if (throwable == null || type == null) {
+ return NOT_FOUND;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ final Throwable[] throwables = getThrowables(throwable);
+ if (fromIndex >= throwables.length) {
+ return NOT_FOUND;
+ }
+ if (subclass) {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.isAssignableFrom(throwables[i].getClass())) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.equals(throwables[i].getClass())) {
+ return i;
+ }
+ }
+ }
+ return NOT_FOUND;
+ }
+
+ /**
+ * Returns the (zero-based) index of the first {@link Throwable}
+ * that matches the specified class (exactly) in the exception chain.
+ * Subclasses of the specified class do not match - see
+ * {@link #indexOfType(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code -1}.
+ * A {@code null} type returns {@code -1}.
+ * No match in the chain returns {@code -1}.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ */
+ public static int indexOfThrowable(final Throwable throwable, final Class<? extends Throwable> clazz) {
+ return indexOf(throwable, clazz, 0, false);
+ }
+
+ /**
+ * Returns the (zero-based) index of the first {@link Throwable}
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do not match - see
+ * {@link #indexOfType(Throwable, Class, int)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code -1}.
+ * A {@code null} type returns {@code -1}.
+ * No match in the chain returns {@code -1}.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns {@code -1}.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns -1
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ */
+ public static int indexOfThrowable(final Throwable throwable, final Class<? extends Throwable> clazz, final int fromIndex) {
+ return indexOf(throwable, clazz, fromIndex, false);
+ }
+
+ /**
+ * Returns the (zero-based) index of the first {@link Throwable}
+ * that matches the specified class or subclass in the exception chain.
+ * Subclasses of the specified class do match - see
+ * {@link #indexOfThrowable(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code -1}.
+ * A {@code null} type returns {@code -1}.
+ * No match in the chain returns {@code -1}.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ * @since 2.1
+ */
+ public static int indexOfType(final Throwable throwable, final Class<? extends Throwable> type) {
+ return indexOf(throwable, type, 0, true);
+ }
+
+ /**
+ * Returns the (zero-based) index of the first {@link Throwable}
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do match - see
+ * {@link #indexOfThrowable(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code -1}.
+ * A {@code null} type returns {@code -1}.
+ * No match in the chain returns {@code -1}.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns {@code -1}.</p>
+ *
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns -1
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns -1
+ * @return the index into the throwable chain, -1 if no match or null input
+ * @since 2.1
+ */
+ public static int indexOfType(final Throwable throwable, final Class<? extends Throwable> type, final int fromIndex) {
+ return indexOf(throwable, type, fromIndex, true);
+ }
+
+ /**
+ * Prints a compact stack trace for the root cause of a throwable
+ * to {@code System.err}.
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to {@code printStackTrace} for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output
+ * @since 2.0
+ */
+ public static void printRootCauseStackTrace(final Throwable throwable) {
+ printRootCauseStackTrace(throwable, System.err);
+ }
+
+ /**
+ * Prints a compact stack trace for the root cause of a throwable.
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to {@code printStackTrace} for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output, may be null
+ * @param printStream the stream to output to, may not be null
+ * @throws NullPointerException if the printStream is {@code null}
+ * @since 2.0
+ */
+ @SuppressWarnings("resource")
+ public static void printRootCauseStackTrace(final Throwable throwable, final PrintStream printStream) {
+ if (throwable == null) {
+ return;
+ }
+ Objects.requireNonNull(printStream, "printStream");
+ getRootCauseStackTraceList(throwable).forEach(printStream::println);
+ printStream.flush();
+ }
+
+ /**
+ * Prints a compact stack trace for the root cause of a throwable.
+ *
+ * <p>The compact stack trace starts with the root cause and prints
+ * stack frames up to the place where it was caught and wrapped.
+ * Then it prints the wrapped exception and continues with stack frames
+ * until the wrapper exception is caught and wrapped again, etc.</p>
+ *
+ * <p>The output of this method is consistent across JDK versions.
+ * Note that this is the opposite order to the JDK1.4 display.</p>
+ *
+ * <p>The method is equivalent to {@code printStackTrace} for throwables
+ * that don't have nested causes.</p>
+ *
+ * @param throwable the throwable to output, may be null
+ * @param printWriter the writer to output to, may not be null
+ * @throws NullPointerException if the printWriter is {@code null}
+ * @since 2.0
+ */
+ @SuppressWarnings("resource")
+ public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter printWriter) {
+ if (throwable == null) {
+ return;
+ }
+ Objects.requireNonNull(printWriter, "printWriter");
+ getRootCauseStackTraceList(throwable).forEach(printWriter::println);
+ printWriter.flush();
+ }
+
+ /**
+ * Removes common frames from the cause trace given the two stack traces.
+ *
+ * @param causeFrames stack trace of a cause throwable
+ * @param wrapperFrames stack trace of a wrapper throwable
+ * @throws NullPointerException if either argument is null
+ * @since 2.0
+ */
+ public static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
+ Objects.requireNonNull(causeFrames, "causeFrames");
+ Objects.requireNonNull(wrapperFrames, "wrapperFrames");
+ int causeFrameIndex = causeFrames.size() - 1;
+ int wrapperFrameIndex = wrapperFrames.size() - 1;
+ while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
+ // Remove the frame from the cause trace if it is the same
+ // as in the wrapper trace
+ final String causeFrame = causeFrames.get(causeFrameIndex);
+ final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
+ if (causeFrame.equals(wrapperFrame)) {
+ causeFrames.remove(causeFrameIndex);
+ }
+ causeFrameIndex--;
+ wrapperFrameIndex--;
+ }
+ }
+
+ /**
+ * Throws a checked exception without adding the exception to the throws
+ * clause of the calling method. This method prevents throws clause
+ * pollution and reduces the clutter of "Caused by" exceptions in the
+ * stacktrace.
+ * <p>
+ * The use of this technique may be controversial, but exceedingly useful to
+ * library developers.
+ * </p>
+ * <pre>
+ * public int propagateExample { // note that there is no throws clause
+ * try {
+ * return invocation(); // throws IOException
+ * } catch (Exception e) {
+ * return ExceptionUtils.rethrow(e); // propagates a checked exception
+ * }
+ * }
+ * </pre>
+ * <p>
+ * This is an alternative to the more conservative approach of wrapping the
+ * checked exception in a RuntimeException:
+ * </p>
+ * <pre>
+ * public int wrapExample { // note that there is no throws clause
+ * try {
+ * return invocation(); // throws IOException
+ * } catch (Error e) {
+ * throw e;
+ * } catch (RuntimeException e) {
+ * throw e; // wraps a checked exception
+ * } catch (Exception e) {
+ * throw new UndeclaredThrowableException(e); // wraps a checked exception
+ * }
+ * }
+ * </pre>
+ * <p>
+ * One downside to using this approach is that the java compiler will not
+ * allow invoking code to specify a checked exception in a catch clause
+ * unless there is some code path within the try block that has invoked a
+ * method declared with that checked exception. If the invoking site wishes
+ * to catch the shaded checked exception, it must either invoke the shaded
+ * code through a method re-declaring the desired checked exception, or
+ * catch Exception and use the instanceof operator. Either of these
+ * techniques are required when interacting with non-java jvm code such as
+ * Jython, Scala, or Groovy, since these languages do not consider any
+ * exceptions as checked.
+ * </p>
+ *
+ * @param throwable
+ * The throwable to rethrow.
+ * @param <R> The type of the returned value.
+ * @return Never actually returned, this generic type matches any type
+ * which the calling site requires. "Returning" the results of this
+ * method, as done in the propagateExample above, will satisfy the
+ * java compiler requirement that all code paths return a value.
+ * @since 3.5
+ * @see #wrapAndThrow(Throwable)
+ */
+ public static <R> R rethrow(final Throwable throwable) {
+ // claim that the typeErasure invocation throws a RuntimeException
+ return ExceptionUtils.<R, RuntimeException>eraseType(throwable);
+ }
+
+ /**
+ * Streams causes of a Throwable.
+ * <p>
+ * A throwable without cause will return a stream containing one element - the input throwable. A throwable with one cause
+ * will return a stream containing two elements. - the input throwable and the cause throwable. A {@code null} throwable
+ * will return a stream of count zero.
+ * </p>
+ *
+ * <p>
+ * This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is
+ * processed until the end is reached, or until the next item in the chain is already in the result set.
+ * </p>
+ *
+ * @param throwable The Throwable to traverse
+ * @return A new Stream of Throwable causes.
+ * @since 3.13.0
+ */
+ public static Stream<Throwable> stream(final Throwable throwable) {
+ // No point building a custom Iterable as it would keep track of visited elements to avoid infinite loops
+ return getThrowableList(throwable).stream();
+ }
+
+ /**
+ * Worker method for the {@code throwableOfType} methods.
+ *
+ * @param <T> the type of Throwable you are searching.
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search, subclasses match, null returns null
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns null
+ * @param subclass if {@code true}, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
+ * using references
+ * @return throwable of the {@code type} within throwables nested within the specified {@code throwable}
+ */
+ private static <T extends Throwable> T throwableOf(final Throwable throwable, final Class<T> type, int fromIndex, final boolean subclass) {
+ if (throwable == null || type == null) {
+ return null;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ final Throwable[] throwables = getThrowables(throwable);
+ if (fromIndex >= throwables.length) {
+ return null;
+ }
+ if (subclass) {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.isAssignableFrom(throwables[i].getClass())) {
+ return type.cast(throwables[i]);
+ }
+ }
+ } else {
+ for (int i = fromIndex; i < throwables.length; i++) {
+ if (type.equals(throwables[i].getClass())) {
+ return type.cast(throwables[i]);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first {@link Throwable}
+ * that matches the specified class (exactly) in the exception chain.
+ * Subclasses of the specified class do not match - see
+ * {@link #throwableOfType(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code null}.
+ * A {@code null} type returns {@code null}.
+ * No match in the chain returns {@code null}.</p>
+ *
+ * @param <T> the type of Throwable you are searching.
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns null
+ * @return the first matching throwable from the throwable chain, null if no match or null input
+ * @since 3.10
+ */
+ public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz) {
+ return throwableOf(throwable, clazz, 0, false);
+ }
+
+ /**
+ * Returns the first {@link Throwable}
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do not match - see
+ * {@link #throwableOfType(Throwable, Class, int)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code null}.
+ * A {@code null} type returns {@code null}.
+ * No match in the chain returns {@code null}.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns {@code null}.</p>
+ *
+ * @param <T> the type of Throwable you are searching.
+ * @param throwable the throwable to inspect, may be null
+ * @param clazz the class to search for, subclasses do not match, null returns null
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns null
+ * @return the first matching throwable from the throwable chain, null if no match or null input
+ * @since 3.10
+ */
+ public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz, final int fromIndex) {
+ return throwableOf(throwable, clazz, fromIndex, false);
+ }
+
+ /**
+ * Returns the throwable of the first {@link Throwable}
+ * that matches the specified class or subclass in the exception chain.
+ * Subclasses of the specified class do match - see
+ * {@link #throwableOfThrowable(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code null}.
+ * A {@code null} type returns {@code null}.
+ * No match in the chain returns {@code null}.</p>
+ *
+ * @param <T> the type of Throwable you are searching.
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns null
+ * @return the first matching throwable from the throwable chain, null if no match or null input
+ * @since 3.10
+ */
+ public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type) {
+ return throwableOf(throwable, type, 0, true);
+ }
+
+ /**
+ * Returns the first {@link Throwable}
+ * that matches the specified type in the exception chain from
+ * a specified index.
+ * Subclasses of the specified class do match - see
+ * {@link #throwableOfThrowable(Throwable, Class)} for the opposite.
+ *
+ * <p>A {@code null} throwable returns {@code null}.
+ * A {@code null} type returns {@code null}.
+ * No match in the chain returns {@code null}.
+ * A negative start index is treated as zero.
+ * A start index greater than the number of throwables returns {@code null}.</p>
+ *
+ * @param <T> the type of Throwable you are searching.
+ * @param throwable the throwable to inspect, may be null
+ * @param type the type to search for, subclasses match, null returns null
+ * @param fromIndex the (zero-based) index of the starting position,
+ * negative treated as zero, larger than chain size returns null
+ * @return the first matching throwable from the throwable chain, null if no match or null input
+ * @since 3.10
+ */
+ public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type, final int fromIndex) {
+ return throwableOf(throwable, type, fromIndex, true);
+ }
+
+ /**
+ * Throws a checked exception without adding the exception to the throws
+ * clause of the calling method. For checked exceptions, this method throws
+ * an UndeclaredThrowableException wrapping the checked exception. For
+ * Errors and RuntimeExceptions, the original exception is rethrown.
+ * <p>
+ * The downside to using this approach is that invoking code which needs to
+ * handle specific checked exceptions must sniff up the exception chain to
+ * determine if the caught exception was caused by the checked exception.
+ * </p>
+ *
+ * @param throwable
+ * The throwable to rethrow.
+ * @param <R> The type of the returned value.
+ * @return Never actually returned, this generic type matches any type
+ * which the calling site requires. "Returning" the results of this
+ * method will satisfy the java compiler requirement that all code
+ * paths return a value.
+ * @since 3.5
+ * @see #rethrow(Throwable)
+ * @see #hasCause(Throwable, Class)
+ */
+ public static <R> R wrapAndThrow(final Throwable throwable) {
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+ if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ throw new UndeclaredThrowableException(throwable);
+ }
+
+ /**
+ * Public constructor allows an instance of {@link ExceptionUtils} to be created, although that is not
+ * normally necessary.
+ */
+ public ExceptionUtils() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/UncheckedException.java b/src/main/java/org/apache/commons/lang3/exception/UncheckedException.java
new file mode 100644
index 000000000..5fdd33742
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/UncheckedException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+/**
+ * Abstracts the concept of wrapping a checked exception as unchecked.
+ * <p>
+ * Subclasses should only be used to wrap checked exception.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public class UncheckedException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessException.java b/src/main/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessException.java
new file mode 100644
index 000000000..509aca871
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/UncheckedIllegalAccessException.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.exception;
+
+/**
+ * Unchecked {@link IllegalAccessException}.
+ *
+ * @since 3.13.0
+ */
+public class UncheckedIllegalAccessException extends UncheckedReflectiveOperationException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedIllegalAccessException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/UncheckedInterruptedException.java b/src/main/java/org/apache/commons/lang3/exception/UncheckedInterruptedException.java
new file mode 100644
index 000000000..4a8f6c0a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/UncheckedInterruptedException.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+/**
+ * Unchecked {@link InterruptedException}.
+ *
+ * @since 3.13.0
+ */
+public class UncheckedInterruptedException extends UncheckedException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedInterruptedException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationException.java b/src/main/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationException.java
new file mode 100644
index 000000000..39b7b53b3
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/UncheckedReflectiveOperationException.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.exception;
+
+/**
+ * Unchecked {@link ReflectiveOperationException}.
+ *
+ * @since 3.13.0
+ */
+public class UncheckedReflectiveOperationException extends UncheckedException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance initialized to the given {@code cause}.
+ *
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A @{code null} value
+ * is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public UncheckedReflectiveOperationException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/exception/package-info.java b/src/main/java/org/apache/commons/lang3/exception/package-info.java
new file mode 100644
index 000000000..1fa0e626d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/exception/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides functionality for Exceptions.
+ * <p>Contains the concept of an exception with context i.e. such an exception will contain a map with keys and values.
+ * This provides an easy way to pass valuable state information at exception time in useful form to a calling process.</p>
+ * <p>Lastly, {@link org.apache.commons.lang3.exception.ExceptionUtils} also contains {@link Throwable} manipulation
+ * and examination routines.</p>
+ *
+ * @since 1.0
+ */
+package org.apache.commons.lang3.exception;
diff --git a/src/main/java/org/apache/commons/lang3/function/BooleanConsumer.java b/src/main/java/org/apache/commons/lang3/function/BooleanConsumer.java
new file mode 100644
index 000000000..9a6147241
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/BooleanConsumer.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.IntConsumer;
+
+/**
+ * A functional interface like {@link IntConsumer} but for {@code boolean}.
+ *
+ * @see IntConsumer
+ * @since 3.13.0
+ */
+@FunctionalInterface
+public interface BooleanConsumer {
+
+ /** NOP singleton */
+ BooleanConsumer NOP = t -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @return The NOP singleton.
+ */
+ static BooleanConsumer nop() {
+ return NOP;
+ }
+
+ /**
+ * Performs this operation on the given argument.
+ *
+ * @param value the input argument
+ */
+ void accept(boolean value);
+
+ /**
+ * Returns a composed {@link BooleanConsumer} that performs, in sequence, this operation followed by the {@code after}
+ * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation.
+ * If performing this operation throws an exception, the {@code after} operation will not be performed.
+ *
+ * @param after the operation to perform after this operation
+ * @return a composed {@link BooleanConsumer} that performs in sequence this operation followed by the {@code after}
+ * operation
+ * @throws NullPointerException if {@code after} is null
+ */
+ default BooleanConsumer andThen(final BooleanConsumer after) {
+ Objects.requireNonNull(after);
+ return (final boolean t) -> {
+ accept(t);
+ after.accept(t);
+ };
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/Consumers.java b/src/main/java/org/apache/commons/lang3/function/Consumers.java
new file mode 100644
index 000000000..19b5efd9e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/Consumers.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Provides {@link Consumer} instances.
+ *
+ * @since 3.13.0
+ */
+public class Consumers {
+
+ /** NOP singleton. */
+ @SuppressWarnings("rawtypes")
+ private static final Consumer NOP = Function.identity()::apply;
+
+ private Consumers() {
+ // No instances.
+ }
+
+ /**
+ * Gets the NOP Consumer singleton.
+ *
+ * @param <T> type type to consume.
+ * @return the NOP Consumer singleton..
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Consumer<T> nop() {
+ return NOP;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/Failable.java b/src/main/java/org/apache/commons/lang3/function/Failable.java
new file mode 100644
index 000000000..efe3f707a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/Failable.java
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.stream.Streams;
+import org.apache.commons.lang3.stream.Streams.FailableStream;
+
+/**
+ * This class provides utility functions, and classes for working with the {@code java.util.function} package, or more
+ * generally, with Java 8 lambdas. More specifically, it attempts to address the fact that lambdas are supposed not to
+ * throw Exceptions, at least not checked Exceptions, AKA instances of {@link Exception}. This enforces the use of
+ * constructs like:
+ *
+ * <pre>
+ * Consumer&lt;java.lang.reflect.Method-&gt; consumer = m -&gt; {
+ * try {
+ * m.invoke(o, args);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * </pre>
+ *
+ * <p>
+ * By replacing a {@link java.util.function.Consumer Consumer&lt;O&gt;} with a {@link FailableConsumer
+ * FailableConsumer&lt;O,? extends Throwable&gt;}, this can be written like follows:
+ * </p>
+ *
+ * <pre>
+ * Functions.accept((m) -&gt; m.invoke(o, args));
+ * </pre>
+ *
+ * <p>
+ * Obviously, the second version is much more concise and the spirit of Lambda expressions is met better than the second
+ * version.
+ * </p>
+ *
+ * @since 3.11
+ */
+public class Failable {
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param object1 the first object to consume by {@code consumer}
+ * @param object2 the second object to consume by {@code consumer}
+ * @param <T> the type of the first argument the consumer accepts
+ * @param <U> the type of the second argument the consumer accepts
+ * @param <E> the type of checked exception the consumer may throw
+ */
+ public static <T, U, E extends Throwable> void accept(final FailableBiConsumer<T, U, E> consumer, final T object1,
+ final U object2) {
+ run(() -> consumer.accept(object1, object2));
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param object the object to consume by {@code consumer}
+ * @param <T> the type the consumer accepts
+ * @param <E> the type of checked exception the consumer may throw
+ */
+ public static <T, E extends Throwable> void accept(final FailableConsumer<T, E> consumer, final T object) {
+ run(() -> consumer.accept(object));
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param value the value to consume by {@code consumer}
+ * @param <E> the type of checked exception the consumer may throw
+ */
+ public static <E extends Throwable> void accept(final FailableDoubleConsumer<E> consumer, final double value) {
+ run(() -> consumer.accept(value));
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param value the value to consume by {@code consumer}
+ * @param <E> the type of checked exception the consumer may throw
+ */
+ public static <E extends Throwable> void accept(final FailableIntConsumer<E> consumer, final int value) {
+ run(() -> consumer.accept(value));
+ }
+
+ /**
+ * Consumes a consumer and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param consumer the consumer to consume
+ * @param value the value to consume by {@code consumer}
+ * @param <E> the type of checked exception the consumer may throw
+ */
+ public static <E extends Throwable> void accept(final FailableLongConsumer<E> consumer, final long value) {
+ run(() -> consumer.accept(value));
+ }
+
+ /**
+ * Applies a function and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param function the function to apply
+ * @param input1 the first input to apply {@code function} on
+ * @param input2 the second input to apply {@code function} on
+ * @param <T> the type of the first argument the function accepts
+ * @param <U> the type of the second argument the function accepts
+ * @param <R> the return type of the function
+ * @param <E> the type of checked exception the function may throw
+ * @return the value returned from the function
+ */
+ public static <T, U, R, E extends Throwable> R apply(final FailableBiFunction<T, U, R, E> function, final T input1,
+ final U input2) {
+ return get(() -> function.apply(input1, input2));
+ }
+
+ /**
+ * Applies a function and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param function the function to apply
+ * @param input the input to apply {@code function} on
+ * @param <T> the type of the argument the function accepts
+ * @param <R> the return type of the function
+ * @param <E> the type of checked exception the function may throw
+ * @return the value returned from the function
+ */
+ public static <T, R, E extends Throwable> R apply(final FailableFunction<T, R, E> function, final T input) {
+ return get(() -> function.apply(input));
+ }
+
+ /**
+ * Applies a function and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param function the function to apply
+ * @param left the first input to apply {@code function} on
+ * @param right the second input to apply {@code function} on
+ * @param <E> the type of checked exception the function may throw
+ * @return the value returned from the function
+ */
+ public static <E extends Throwable> double applyAsDouble(final FailableDoubleBinaryOperator<E> function,
+ final double left, final double right) {
+ return getAsDouble(() -> function.applyAsDouble(left, right));
+ }
+
+ /**
+ * Converts the given {@link FailableBiConsumer} into a standard {@link BiConsumer}.
+ *
+ * @param <T> the type of the first argument of the consumers
+ * @param <U> the type of the second argument of the consumers
+ * @param consumer a failable {@link BiConsumer}
+ * @return a standard {@link BiConsumer}
+ */
+ public static <T, U> BiConsumer<T, U> asBiConsumer(final FailableBiConsumer<T, U, ?> consumer) {
+ return (input1, input2) -> accept(consumer, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableBiFunction} into a standard {@link BiFunction}.
+ *
+ * @param <T> the type of the first argument of the input of the functions
+ * @param <U> the type of the second argument of the input of the functions
+ * @param <R> the type of the output of the functions
+ * @param function a {@link FailableBiFunction}
+ * @return a standard {@link BiFunction}
+ */
+ public static <T, U, R> BiFunction<T, U, R> asBiFunction(final FailableBiFunction<T, U, R, ?> function) {
+ return (input1, input2) -> apply(function, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableBiPredicate} into a standard {@link BiPredicate}.
+ *
+ * @param <T> the type of the first argument used by the predicates
+ * @param <U> the type of the second argument used by the predicates
+ * @param predicate a {@link FailableBiPredicate}
+ * @return a standard {@link BiPredicate}
+ */
+ public static <T, U> BiPredicate<T, U> asBiPredicate(final FailableBiPredicate<T, U, ?> predicate) {
+ return (input1, input2) -> test(predicate, input1, input2);
+ }
+
+ /**
+ * Converts the given {@link FailableCallable} into a standard {@link Callable}.
+ *
+ * @param <V> the type used by the callables
+ * @param callable a {@link FailableCallable}
+ * @return a standard {@link Callable}
+ */
+ public static <V> Callable<V> asCallable(final FailableCallable<V, ?> callable) {
+ return () -> call(callable);
+ }
+
+ /**
+ * Converts the given {@link FailableConsumer} into a standard {@link Consumer}.
+ *
+ * @param <T> the type used by the consumers
+ * @param consumer a {@link FailableConsumer}
+ * @return a standard {@link Consumer}
+ */
+ public static <T> Consumer<T> asConsumer(final FailableConsumer<T, ?> consumer) {
+ return input -> accept(consumer, input);
+ }
+
+ /**
+ * Converts the given {@link FailableFunction} into a standard {@link Function}.
+ *
+ * @param <T> the type of the input of the functions
+ * @param <R> the type of the output of the functions
+ * @param function a {code FailableFunction}
+ * @return a standard {@link Function}
+ */
+ public static <T, R> Function<T, R> asFunction(final FailableFunction<T, R, ?> function) {
+ return input -> apply(function, input);
+ }
+
+ /**
+ * Converts the given {@link FailablePredicate} into a standard {@link Predicate}.
+ *
+ * @param <T> the type used by the predicates
+ * @param predicate a {@link FailablePredicate}
+ * @return a standard {@link Predicate}
+ */
+ public static <T> Predicate<T> asPredicate(final FailablePredicate<T, ?> predicate) {
+ return input -> test(predicate, input);
+ }
+
+ /**
+ * Converts the given {@link FailableRunnable} into a standard {@link Runnable}.
+ *
+ * @param runnable a {@link FailableRunnable}
+ * @return a standard {@link Runnable}
+ */
+ public static Runnable asRunnable(final FailableRunnable<?> runnable) {
+ return () -> run(runnable);
+ }
+
+ /**
+ * Converts the given {@link FailableSupplier} into a standard {@link Supplier}.
+ *
+ * @param <T> the type supplied by the suppliers
+ * @param supplier a {@link FailableSupplier}
+ * @return a standard {@link Supplier}
+ */
+ public static <T> Supplier<T> asSupplier(final FailableSupplier<T, ?> supplier) {
+ return () -> get(supplier);
+ }
+
+ /**
+ * Calls a callable and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param callable the callable to call
+ * @param <V> the return type of the callable
+ * @param <E> the type of checked exception the callable may throw
+ * @return the value returned from the callable
+ */
+ public static <V, E extends Throwable> V call(final FailableCallable<V, E> callable) {
+ return get(callable::call);
+ }
+
+ /**
+ * Invokes a supplier, and returns the result.
+ *
+ * @param supplier The supplier to invoke.
+ * @param <T> The suppliers output type.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The object, which has been created by the supplier
+ */
+ public static <T, E extends Throwable> T get(final FailableSupplier<T, E> supplier) {
+ try {
+ return supplier.get();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes a boolean supplier, and returns the result.
+ *
+ * @param supplier The boolean supplier to invoke.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The boolean, which has been created by the supplier
+ */
+ public static <E extends Throwable> boolean getAsBoolean(final FailableBooleanSupplier<E> supplier) {
+ try {
+ return supplier.getAsBoolean();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes a double supplier, and returns the result.
+ *
+ * @param supplier The double supplier to invoke.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The double, which has been created by the supplier
+ */
+ public static <E extends Throwable> double getAsDouble(final FailableDoubleSupplier<E> supplier) {
+ try {
+ return supplier.getAsDouble();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes an int supplier, and returns the result.
+ *
+ * @param supplier The int supplier to invoke.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The int, which has been created by the supplier
+ */
+ public static <E extends Throwable> int getAsInt(final FailableIntSupplier<E> supplier) {
+ try {
+ return supplier.getAsInt();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes a long supplier, and returns the result.
+ *
+ * @param supplier The long supplier to invoke.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The long, which has been created by the supplier
+ */
+ public static <E extends Throwable> long getAsLong(final FailableLongSupplier<E> supplier) {
+ try {
+ return supplier.getAsLong();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Invokes a short supplier, and returns the result.
+ *
+ * @param supplier The short supplier to invoke.
+ * @param <E> The type of checked exception, which the supplier can throw.
+ * @return The short, which has been created by the supplier
+ */
+ public static <E extends Throwable> short getAsShort(final FailableShortSupplier<E> supplier) {
+ try {
+ return supplier.getAsShort();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Rethrows a {@link Throwable} as an unchecked exception. If the argument is already unchecked, namely a
+ * {@link RuntimeException} or {@link Error} then the argument will be rethrown without modification. If the
+ * exception is {@link IOException} then it will be wrapped into a {@link UncheckedIOException}. In every other
+ * cases the exception will be wrapped into a {@code
+ * UndeclaredThrowableException}
+ *
+ * <p>
+ * Note that there is a declared return type for this method, even though it never returns. The reason for that is
+ * to support the usual pattern:
+ * </p>
+ *
+ * <pre>
+ * throw rethrow(myUncheckedException);
+ * </pre>
+ *
+ * <p>
+ * instead of just calling the method. This pattern may help the Java compiler to recognize that at that point an
+ * exception will be thrown and the code flow analysis will not demand otherwise mandatory commands that could
+ * follow the method call, like a {@code return} statement from a value returning method.
+ * </p>
+ *
+ * @param throwable The throwable to rethrow possibly wrapped into an unchecked exception
+ * @return Never returns anything, this method never terminates normally.
+ */
+ public static RuntimeException rethrow(final Throwable throwable) {
+ Objects.requireNonNull(throwable, "throwable");
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+ if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ if (throwable instanceof IOException) {
+ throw new UncheckedIOException((IOException) throwable);
+ }
+ throw new UndeclaredThrowableException(throwable);
+ }
+
+ /**
+ * Runs a runnable and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param runnable The runnable to run
+ * @param <E> the type of checked exception the runnable may throw
+ */
+ public static <E extends Throwable> void run(final FailableRunnable<E> runnable) {
+ try {
+ runnable.run();
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+
+ /**
+ * Converts the given collection into a {@link FailableStream}. The {@link FailableStream} consists of the
+ * collections elements. Shortcut for
+ *
+ * <pre>
+ * Functions.stream(collection.stream());
+ * </pre>
+ *
+ * @param collection The collection, which is being converted into a {@link FailableStream}.
+ * @param <E> The collections element type. (In turn, the result streams element type.)
+ * @return The created {@link FailableStream}.
+ */
+ public static <E> FailableStream<E> stream(final Collection<E> collection) {
+ return new FailableStream<>(collection.stream());
+ }
+
+ /**
+ * Converts the given stream into a {@link FailableStream}. The {@link FailableStream} consists of the same
+ * elements, than the input stream. However, failable lambdas, like {@link FailablePredicate},
+ * {@link FailableFunction}, and {@link FailableConsumer} may be applied, rather than {@link Predicate},
+ * {@link Function}, {@link Consumer}, etc.
+ *
+ * @param stream The stream, which is being converted into a {@link FailableStream}.
+ * @param <T> The streams element type.
+ * @return The created {@link FailableStream}.
+ */
+ public static <T> FailableStream<T> stream(final Stream<T> stream) {
+ return new FailableStream<>(stream);
+ }
+
+ /**
+ * Tests a predicate and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param predicate the predicate to test
+ * @param object1 the first input to test by {@code predicate}
+ * @param object2 the second input to test by {@code predicate}
+ * @param <T> the type of the first argument the predicate tests
+ * @param <U> the type of the second argument the predicate tests
+ * @param <E> the type of checked exception the predicate may throw
+ * @return the boolean value returned by the predicate
+ */
+ public static <T, U, E extends Throwable> boolean test(final FailableBiPredicate<T, U, E> predicate,
+ final T object1, final U object2) {
+ return getAsBoolean(() -> predicate.test(object1, object2));
+ }
+
+ /**
+ * Tests a predicate and rethrows any exception as a {@link RuntimeException}.
+ *
+ * @param predicate the predicate to test
+ * @param object the input to test by {@code predicate}
+ * @param <T> the type of argument the predicate tests
+ * @param <E> the type of checked exception the predicate may throw
+ * @return the boolean value returned by the predicate
+ */
+ public static <T, E extends Throwable> boolean test(final FailablePredicate<T, E> predicate, final T object) {
+ return getAsBoolean(() -> predicate.test(object));
+ }
+
+ /**
+ * A simple try-with-resources implementation, that can be used, if your objects do not implement the
+ * {@link AutoCloseable} interface. The method executes the {@code action}. The method guarantees, that <em>all</em>
+ * the {@code resources} are being executed, in the given order, afterwards, and regardless of success, or failure.
+ * If either the original action, or any of the resource action fails, then the <em>first</em> failure (AKA
+ * {@link Throwable}) is rethrown. Example use:
+ *
+ * <pre>
+ * final FileInputStream fis = new FileInputStream("my.file");
+ * Functions.tryWithResources(useInputStream(fis), null, () -&gt; fis.close());
+ * </pre>
+ *
+ * @param action The action to execute. This object <em>will</em> always be invoked.
+ * @param errorHandler An optional error handler, which will be invoked finally, if any error occurred. The error
+ * handler will receive the first error, AKA {@link Throwable}.
+ * @param resources The resource actions to execute. <em>All</em> resource actions will be invoked, in the given
+ * order. A resource action is an instance of {@link FailableRunnable}, which will be executed.
+ * @see #tryWithResources(FailableRunnable, FailableRunnable...)
+ */
+ @SafeVarargs
+ public static void tryWithResources(final FailableRunnable<? extends Throwable> action,
+ final FailableConsumer<Throwable, ? extends Throwable> errorHandler,
+ final FailableRunnable<? extends Throwable>... resources) {
+ final FailableConsumer<Throwable, ? extends Throwable> actualErrorHandler;
+ if (errorHandler == null) {
+ actualErrorHandler = Failable::rethrow;
+ } else {
+ actualErrorHandler = errorHandler;
+ }
+ Streams.of(resources).forEach(r -> Objects.requireNonNull(r, "runnable"));
+ Throwable th = null;
+ try {
+ action.run();
+ } catch (final Throwable t) {
+ th = t;
+ }
+ if (resources != null) {
+ for (final FailableRunnable<?> runnable : resources) {
+ try {
+ runnable.run();
+ } catch (final Throwable t) {
+ if (th == null) {
+ th = t;
+ }
+ }
+ }
+ }
+ if (th != null) {
+ try {
+ actualErrorHandler.accept(th);
+ } catch (final Throwable t) {
+ throw rethrow(t);
+ }
+ }
+ }
+
+ /**
+ * A simple try-with-resources implementation, that can be used, if your objects do not implement the
+ * {@link AutoCloseable} interface. The method executes the {@code action}. The method guarantees, that <em>all</em>
+ * the {@code resources} are being executed, in the given order, afterwards, and regardless of success, or failure.
+ * If either the original action, or any of the resource action fails, then the <em>first</em> failure (AKA
+ * {@link Throwable}) is rethrown. Example use:
+ *
+ * <pre>
+ * final FileInputStream fis = new FileInputStream("my.file");
+ * Functions.tryWithResources(useInputStream(fis), () -&gt; fis.close());
+ * </pre>
+ *
+ * @param action The action to execute. This object <em>will</em> always be invoked.
+ * @param resources The resource actions to execute. <em>All</em> resource actions will be invoked, in the given
+ * order. A resource action is an instance of {@link FailableRunnable}, which will be executed.
+ * @see #tryWithResources(FailableRunnable, FailableConsumer, FailableRunnable...)
+ */
+ @SafeVarargs
+ public static void tryWithResources(final FailableRunnable<? extends Throwable> action,
+ final FailableRunnable<? extends Throwable>... resources) {
+ tryWithResources(action, null, resources);
+ }
+
+ private Failable() {
+ // empty
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java
new file mode 100644
index 000000000..823a6496d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableBiConsumer.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+/**
+ * A functional interface like {@link BiConsumer} that declares a {@link Throwable}.
+ *
+ * @param <T> Consumed type 1.
+ * @param <U> Consumed type 2.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableBiConsumer<T, U, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableBiConsumer NOP = (t, u) -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <U> Consumed type 2.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableBiConsumer<T, U, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param t the first parameter for the consumable to accept
+ * @param u the second parameter for the consumable to accept
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(T t, U u) throws E;
+
+ /**
+ * Returns a composed {@link FailableBiConsumer} like {@link BiConsumer#andThen(BiConsumer)}.
+ *
+ * @param after the operation to perform after this one.
+ * @return a composed {@link FailableBiConsumer} like {@link BiConsumer#andThen(BiConsumer)}.
+ * @throws NullPointerException when {@code after} is null.
+ */
+ default FailableBiConsumer<T, U, E> andThen(final FailableBiConsumer<? super T, ? super U, E> after) {
+ Objects.requireNonNull(after);
+ return (t, u) -> {
+ accept(t, u);
+ after.accept(t, u);
+ };
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java
new file mode 100644
index 000000000..cabcd1921
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableBiFunction.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * A functional interface like {@link BiFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> Input type 1.
+ * @param <U> Input type 2.
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableBiFunction<T, U, R, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableBiFunction NOP = (t, u) -> null;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <U> Consumed type 2.
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, R, E extends Throwable> FailableBiFunction<T, U, R, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Returns a composed {@link FailableBiFunction} that like {@link BiFunction#andThen(Function)}.
+ *
+ * @param <V> the output type of the {@code after} function, and of the composed function.
+ * @param after the operation to perform after this one.
+ * @return a composed {@link FailableBiFunction} that like {@link BiFunction#andThen(Function)}.
+ * @throws NullPointerException when {@code after} is null.
+ */
+ default <V> FailableBiFunction<T, U, V, E> andThen(final FailableFunction<? super R, ? extends V, E> after) {
+ Objects.requireNonNull(after);
+ return (final T t, final U u) -> after.apply(apply(t, u));
+ }
+
+ /**
+ * Applies this function.
+ *
+ * @param input1 the first input for the function
+ * @param input2 the second input for the function
+ * @return the result of the function
+ * @throws E Thrown when the function fails.
+ */
+ R apply(T input1, U input2) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java b/src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java
new file mode 100644
index 000000000..9fff9180e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableBiPredicate.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.BiPredicate;
+
+/**
+ * A functional interface like {@link BiPredicate} that declares a {@link Throwable}.
+ *
+ * @param <T> Predicate type 1.
+ * @param <U> Predicate type 2.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableBiPredicate<T, U, E extends Throwable> {
+
+ /** FALSE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableBiPredicate FALSE = (t, u) -> false;
+
+ /** TRUE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableBiPredicate TRUE = (t, u) -> true;
+
+ /**
+ * Returns The FALSE singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <U> Consumed type 2.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableBiPredicate<T, U, E> falsePredicate() {
+ return FALSE;
+ }
+
+ /**
+ * Returns The TRUE singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <U> Consumed type 2.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableBiPredicate<T, U, E> truePredicate() {
+ return TRUE;
+ }
+
+ /**
+ * Returns a composed {@link FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}.
+ *
+ * @param other a predicate that will be logically-ANDed with this predicate.
+ * @return a composed {@link FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableBiPredicate<T, U, E> and(final FailableBiPredicate<? super T, ? super U, E> other) {
+ Objects.requireNonNull(other);
+ return (final T t, final U u) -> test(t, u) && other.test(t, u);
+ }
+
+ /**
+ * Returns a predicate that negates this predicate.
+ *
+ * @return a predicate that negates this predicate.
+ */
+ default FailableBiPredicate<T, U, E> negate() {
+ return (final T t, final U u) -> !test(t, u);
+ }
+
+ /**
+ * Returns a composed {@link FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}.
+ *
+ * @param other a predicate that will be logically-ORed with this predicate.
+ * @return a composed {@link FailableBiPredicate} like {@link BiPredicate#and(BiPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableBiPredicate<T, U, E> or(final FailableBiPredicate<? super T, ? super U, E> other) {
+ Objects.requireNonNull(other);
+ return (final T t, final U u) -> test(t, u) || other.test(t, u);
+ }
+
+ /**
+ * Tests the predicate.
+ *
+ * @param object1 the first object to test the predicate on
+ * @param object2 the second object to test the predicate on
+ * @return the predicate's evaluation
+ * @throws E Thrown when this predicate fails.
+ */
+ boolean test(T object1, U object2) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java
new file mode 100644
index 000000000..1e5dce262
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableBooleanSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * A functional interface like {@link BooleanSupplier} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableBooleanSupplier<E extends Throwable> {
+
+ /**
+ * Supplies a boolean.
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ boolean getAsBoolean() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableCallable.java b/src/main/java/org/apache/commons/lang3/function/FailableCallable.java
new file mode 100644
index 000000000..0ac99a0ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableCallable.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+/**
+ * A functional interface like {@link java.util.concurrent.Callable} that declares a {@link Throwable}.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableCallable<R, E extends Throwable> {
+
+ /**
+ * Calls the callable.
+ *
+ * @return The value returned from the callable
+ * @throws E if the callable fails
+ */
+ R call() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableConsumer.java
new file mode 100644
index 000000000..9565a8f15
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableConsumer.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A functional interface like {@link Consumer} that declares a {@link Throwable}.
+ *
+ * <p>
+ * This is a functional interface whose functional method is {@link #accept(Object)}.
+ * </p>
+ *
+ * @param <T> the type of the input to the operation
+ * @param <E> Thrown exception type.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableConsumer<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableConsumer NOP = Function.identity()::apply;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableConsumer<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the parameter for the consumable to accept
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(T object) throws E;
+
+ /**
+ * Returns a composed {@link Consumer} like {@link Consumer#andThen(Consumer)}.
+ *
+ * @param after the operation to perform after this operation
+ * @return a composed {@link Consumer} like {@link Consumer#andThen(Consumer)}.
+ * @throws NullPointerException when {@code after} is null
+ */
+ default FailableConsumer<T, E> andThen(final FailableConsumer<? super T, E> after) {
+ Objects.requireNonNull(after);
+ return (final T t) -> {
+ accept(t);
+ after.accept(t);
+ };
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java
new file mode 100644
index 000000000..0f23e4324
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleBinaryOperator.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.DoubleBinaryOperator;
+
+/**
+ * A functional interface like {@link DoubleBinaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleBinaryOperator<E extends Throwable> {
+
+ /**
+ * Applies this operator to the given operands.
+ *
+ * @param left the first operand
+ * @param right the second operand
+ * @return the operator result
+ * @throws E if the operation fails
+ */
+ double applyAsDouble(double left, double right) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java
new file mode 100644
index 000000000..88229ff60
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleConsumer.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.DoubleConsumer;
+
+/**
+ * A functional interface like {@link DoubleConsumer} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleConsumer<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoubleConsumer NOP = t -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoubleConsumer<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param value the parameter for the consumable to accept
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(double value) throws E;
+
+ /**
+ * Returns a composed {@link FailableDoubleConsumer} like {@link DoubleConsumer#andThen(DoubleConsumer)}.
+ *
+ * @param after the operation to perform after this one.
+ * @return a composed {@link FailableDoubleConsumer} like {@link DoubleConsumer#andThen(DoubleConsumer)}.
+ * @throws NullPointerException when {@code after} is null.
+ */
+ default FailableDoubleConsumer<E> andThen(final FailableDoubleConsumer<E> after) {
+ Objects.requireNonNull(after);
+ return (final double t) -> {
+ accept(t);
+ after.accept(t);
+ };
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java
new file mode 100644
index 000000000..cdca5accd
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.DoubleFunction;
+
+/**
+ * A functional interface like {@link DoubleFunction} that declares a {@link Throwable}.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleFunction<R, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoubleFunction NOP = t -> null;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <R, E extends Throwable> FailableDoubleFunction<R, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function.
+ *
+ * @param input the input for the function
+ * @return the result of the function
+ * @throws E Thrown when the function fails.
+ */
+ R apply(double input) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java b/src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java
new file mode 100644
index 000000000..e54f501a7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoublePredicate.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.DoublePredicate;
+
+/**
+ * A functional interface like {@link DoublePredicate} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoublePredicate<E extends Throwable> {
+
+ /** FALSE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoublePredicate FALSE = t -> false;
+
+ /** TRUE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoublePredicate TRUE = t -> true;
+
+ /**
+ * Returns The FALSE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoublePredicate<E> falsePredicate() {
+ return FALSE;
+ }
+
+ /**
+ * Returns The TRUE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoublePredicate<E> truePredicate() {
+ return TRUE;
+ }
+
+ /**
+ * Returns a composed {@link FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}.
+ *
+ * @param other a predicate that will be logically-ANDed with this predicate.
+ * @return a composed {@link FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableDoublePredicate<E> and(final FailableDoublePredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) && other.test(t);
+ }
+
+ /**
+ * Returns a predicate that negates this predicate.
+ *
+ * @return a predicate that negates this predicate.
+ */
+ default FailableDoublePredicate<E> negate() {
+ return t -> !test(t);
+ }
+
+ /**
+ * Returns a composed {@link FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}.
+ *
+ * @param other a predicate that will be logically-ORed with this predicate.
+ * @return a composed {@link FailableDoublePredicate} like {@link DoublePredicate#and(DoublePredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableDoublePredicate<E> or(final FailableDoublePredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) || other.test(t);
+ }
+
+ /**
+ * Tests the predicate.
+ *
+ * @param value the parameter for the predicate to accept.
+ * @return {@code true} if the input argument matches the predicate, {@code false} otherwise.
+ * @throws E Thrown when the consumer fails.
+ */
+ boolean test(double value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java
new file mode 100644
index 000000000..1f923d9bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.DoubleSupplier;
+
+/**
+ * A functional interface like {@link DoubleSupplier} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleSupplier<E extends Throwable> {
+
+ /**
+ * Supplies a double.
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ double getAsDouble() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java
new file mode 100644
index 000000000..e8a247c62
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleToIntFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.DoubleToIntFunction;
+
+/**
+ * A functional interface like {@link DoubleToIntFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleToIntFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoubleToIntFunction NOP = t -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoubleToIntFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ int applyAsInt(double value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java
new file mode 100644
index 000000000..eeb3f3972
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleToLongFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.DoubleToLongFunction;
+
+/**
+ * A functional interface like {@link DoubleToLongFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableDoubleToLongFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoubleToLongFunction NOP = t -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoubleToLongFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E if the operation fails
+ */
+ int applyAsLong(double value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java
new file mode 100644
index 000000000..91f98c6c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableDoubleUnaryOperator.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * A functional interface like {@link DoubleUnaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+public interface FailableDoubleUnaryOperator<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableDoubleUnaryOperator NOP = t -> 0d;
+
+ /**
+ * Returns a unary operator that always returns its input argument.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return a unary operator that always returns its input argument
+ */
+ static <E extends Throwable> FailableDoubleUnaryOperator<E> identity() {
+ return t -> t;
+ }
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableDoubleUnaryOperator<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Returns a composed {@link FailableDoubleUnaryOperator} like
+ * {@link DoubleUnaryOperator#andThen(DoubleUnaryOperator)}.
+ *
+ * @param after the operator to apply after this one.
+ * @return a composed {@link FailableDoubleUnaryOperator} like
+ * {@link DoubleUnaryOperator#andThen(DoubleUnaryOperator)}.
+ * @throws NullPointerException if after is null.
+ * @see #compose(FailableDoubleUnaryOperator)
+ */
+ default FailableDoubleUnaryOperator<E> andThen(final FailableDoubleUnaryOperator<E> after) {
+ Objects.requireNonNull(after);
+ return (final double t) -> after.applyAsDouble(applyAsDouble(t));
+ }
+
+ /**
+ * Applies this operator to the given operand.
+ *
+ * @param operand the operand
+ * @return the operator result
+ * @throws E Thrown when a consumer fails.
+ */
+ double applyAsDouble(double operand) throws E;
+
+ /**
+ * Returns a composed {@link FailableDoubleUnaryOperator} like
+ * {@link DoubleUnaryOperator#compose(DoubleUnaryOperator)}.
+ *
+ * @param before the operator to apply before this one.
+ * @return a composed {@link FailableDoubleUnaryOperator} like
+ * {@link DoubleUnaryOperator#compose(DoubleUnaryOperator)}.
+ * @throws NullPointerException if before is null.
+ * @see #andThen(FailableDoubleUnaryOperator)
+ */
+ default FailableDoubleUnaryOperator<E> compose(final FailableDoubleUnaryOperator<E> before) {
+ Objects.requireNonNull(before);
+ return (final double v) -> applyAsDouble(before.applyAsDouble(v));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableFunction.java
new file mode 100644
index 000000000..ef088c960
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableFunction.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A functional interface like {@link Function} that declares a {@link Throwable}.
+ *
+ * @param <T> Input type 1.
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableFunction<T, R, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableFunction NOP = t -> null;
+
+ /**
+ * Returns a function that always returns its input argument.
+ *
+ * @param <T> the type of the input and output objects to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return a function that always returns its input argument
+ */
+ static <T, E extends Throwable> FailableFunction<T, T, E> identity() {
+ return t -> t;
+ }
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> Consumed type 1.
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, R, E extends Throwable> FailableFunction<T, R, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Returns a composed {@link FailableFunction} like {@link Function#andThen(Function)}.
+ *
+ * @param <V> the output type of the {@code after} function, and of the composed function.
+ * @return a composed {@link FailableFunction} like {@link Function#andThen(Function)}.
+ * @param after the operation to perform after this one.
+ * @throws NullPointerException when {@code after} is null.
+ */
+ default <V> FailableFunction<T, V, E> andThen(final FailableFunction<? super R, ? extends V, E> after) {
+ Objects.requireNonNull(after);
+ return (final T t) -> after.apply(apply(t));
+ }
+
+ /**
+ * Applies this function.
+ *
+ * @param input the input for the function
+ * @return the result of the function
+ * @throws E Thrown when the function fails.
+ */
+ R apply(T input) throws E;
+
+ /**
+ * Returns a composed {@link FailableFunction} like {@link Function#compose(Function)}.
+ *
+ * @param <V> the input type to the {@code before} function, and to the composed function.
+ * @param before the operator to apply before this one.
+ * @return a composed {@link FailableFunction} like {@link Function#compose(Function)}.
+ * @throws NullPointerException if before is null.
+ * @see #andThen(FailableFunction)
+ */
+ default <V> FailableFunction<V, R, E> compose(final FailableFunction<? super V, ? extends T, E> before) {
+ Objects.requireNonNull(before);
+ return (final V v) -> apply(before.apply(v));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java
new file mode 100644
index 000000000..8f0015a50
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntBinaryOperator.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntBinaryOperator;
+
+/**
+ * A functional interface like {@link IntBinaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntBinaryOperator<E extends Throwable> {
+
+ /**
+ * Applies this operator to the given operands.
+ *
+ * @param left the first operand
+ * @param right the second operand
+ * @return the operator result
+ * @throws E if the operation fails
+ */
+ int applyAsInt(int left, int right) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java
new file mode 100644
index 000000000..a9330e1f2
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntConsumer.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.IntConsumer;
+
+/**
+ * A functional interface like {@link IntConsumer} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntConsumer<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntConsumer NOP = t -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntConsumer<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param value the parameter for the consumable to accept
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(int value) throws E;
+
+ /**
+ * Returns a composed {@link FailableIntConsumer} like {@link IntConsumer#andThen(IntConsumer)}.
+ *
+ * @param after the operation to perform after this one.
+ * @return a composed {@link FailableLongConsumer} like {@link IntConsumer#andThen(IntConsumer)}.
+ * @throws NullPointerException if {@code after} is null
+ */
+ default FailableIntConsumer<E> andThen(final FailableIntConsumer<E> after) {
+ Objects.requireNonNull(after);
+ return (final int t) -> {
+ accept(t);
+ after.accept(t);
+ };
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java
new file mode 100644
index 000000000..7f173defe
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntFunction;
+
+/**
+ * A functional interface like {@link IntFunction} that declares a {@link Throwable}.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntFunction<R, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntFunction NOP = t -> null;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <R, E extends Throwable> FailableIntFunction<R, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function.
+ *
+ * @param input the input for the function
+ * @return the result of the function
+ * @throws E Thrown when the function fails.
+ */
+ R apply(int input) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java b/src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java
new file mode 100644
index 000000000..1438e60f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntPredicate.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.IntPredicate;
+
+/**
+ * A functional interface like {@link IntPredicate} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntPredicate<E extends Throwable> {
+
+ /** FALSE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntPredicate FALSE = t -> false;
+
+ /** TRUE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntPredicate TRUE = t -> true;
+
+ /**
+ * Returns The FALSE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntPredicate<E> falsePredicate() {
+ return FALSE;
+ }
+
+ /**
+ * Returns The TRUE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntPredicate<E> truePredicate() {
+ return TRUE;
+ }
+
+ /**
+ * Returns a composed {@link FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}.
+ *
+ * @param other a predicate that will be logically-ANDed with this predicate.
+ * @return a composed {@link FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableIntPredicate<E> and(final FailableIntPredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) && other.test(t);
+ }
+
+ /**
+ * Returns a predicate that negates this predicate.
+ *
+ * @return a predicate that negates this predicate.
+ */
+ default FailableIntPredicate<E> negate() {
+ return t -> !test(t);
+ }
+
+ /**
+ * Returns a composed {@link FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}.
+ *
+ * @param other a predicate that will be logically-ORed with this predicate.
+ * @return a composed {@link FailableIntPredicate} like {@link IntPredicate#and(IntPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableIntPredicate<E> or(final FailableIntPredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) || other.test(t);
+ }
+
+ /**
+ * Tests the predicate.
+ *
+ * @param value the parameter for the predicate to accept.
+ * @return {@code true} if the input argument matches the predicate, {@code false} otherwise.
+ * @throws E Thrown when the consumer fails.
+ */
+ boolean test(int value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java
new file mode 100644
index 000000000..aa8ce8a19
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntSupplier;
+
+/**
+ * A functional interface like {@link IntSupplier} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntSupplier<E extends Throwable> {
+
+ /**
+ * Supplies an int.
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ int getAsInt() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java
new file mode 100644
index 000000000..6d96cd700
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntToDoubleFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntToDoubleFunction;
+
+/**
+ * A functional interface like {@link IntToDoubleFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntToDoubleFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntToDoubleFunction NOP = t -> 0d;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntToDoubleFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ double applyAsDouble(int value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java
new file mode 100644
index 000000000..4693fb56a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntToLongFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntToLongFunction;
+
+/**
+ * A functional interface like {@link IntToLongFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableIntToLongFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntToLongFunction NOP = t -> 0L;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntToLongFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ long applyAsLong(int value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java
new file mode 100644
index 000000000..a0d3cedc4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableIntUnaryOperator.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.IntUnaryOperator;
+
+/**
+ * A functional interface like {@link IntUnaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+public interface FailableIntUnaryOperator<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableIntUnaryOperator NOP = t -> 0;
+
+ /**
+ * Returns a unary operator that always returns its input argument.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return a unary operator that always returns its input argument
+ */
+ static <E extends Throwable> FailableIntUnaryOperator<E> identity() {
+ return t -> t;
+ }
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableIntUnaryOperator<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Returns a composed {@link FailableDoubleUnaryOperator} like {@link IntUnaryOperator#andThen(IntUnaryOperator)}.
+ *
+ * @param after the operator to apply after this one.
+ * @return a composed {@link FailableIntUnaryOperator} like {@link IntUnaryOperator#andThen(IntUnaryOperator)}.
+ * @throws NullPointerException if after is null.
+ * @see #compose(FailableIntUnaryOperator)
+ */
+ default FailableIntUnaryOperator<E> andThen(final FailableIntUnaryOperator<E> after) {
+ Objects.requireNonNull(after);
+ return (final int t) -> after.applyAsInt(applyAsInt(t));
+ }
+
+ /**
+ * Applies this operator to the given operand.
+ *
+ * @param operand the operand
+ * @return the operator result
+ * @throws E Thrown when a consumer fails.
+ */
+ int applyAsInt(int operand) throws E;
+
+ /**
+ * Returns a composed {@link FailableIntUnaryOperator} like {@link IntUnaryOperator#compose(IntUnaryOperator)}.
+ *
+ * @param before the operator to apply before this one.
+ * @return a composed {@link FailableIntUnaryOperator} like {@link IntUnaryOperator#compose(IntUnaryOperator)}.
+ * @throws NullPointerException if before is null.
+ * @see #andThen(FailableIntUnaryOperator)
+ */
+ default FailableIntUnaryOperator<E> compose(final FailableIntUnaryOperator<E> before) {
+ Objects.requireNonNull(before);
+ return (final int v) -> applyAsInt(before.applyAsInt(v));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java
new file mode 100644
index 000000000..76b121429
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongBinaryOperator.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.LongBinaryOperator;
+
+/**
+ * A functional interface like {@link LongBinaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongBinaryOperator<E extends Throwable> {
+
+ /**
+ * Applies this operator to the given operands.
+ *
+ * @param left the first operand
+ * @param right the second operand
+ * @return the operator result
+ * @throws E if the operation fails
+ */
+ long applyAsLong(long left, long right) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java
new file mode 100644
index 000000000..946b4acf1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongConsumer.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.LongConsumer;
+
+/**
+ * A functional interface like {@link LongConsumer} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongConsumer<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongConsumer NOP = t -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongConsumer<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the parameter for the consumable to accept
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(long object) throws E;
+
+ /**
+ * Returns a composed {@link FailableLongConsumer} like {@link LongConsumer#andThen(LongConsumer)}.
+ *
+ * @param after the operation to perform after this one.
+ * @return a composed {@link FailableLongConsumer} like {@link LongConsumer#andThen(LongConsumer)}.
+ * @throws NullPointerException if {@code after} is null
+ */
+ default FailableLongConsumer<E> andThen(final FailableLongConsumer<E> after) {
+ Objects.requireNonNull(after);
+ return (final long t) -> {
+ accept(t);
+ after.accept(t);
+ };
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java
new file mode 100644
index 000000000..7ba5a12d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.LongFunction;
+
+/**
+ * A functional interface like {@link LongFunction} that declares a {@link Throwable}.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongFunction<R, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongFunction NOP = t -> null;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <R, E extends Throwable> FailableLongFunction<R, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function.
+ *
+ * @param input the input for the function
+ * @return the result of the function
+ * @throws E Thrown when the function fails.
+ */
+ R apply(long input) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java b/src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java
new file mode 100644
index 000000000..426d42891
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongPredicate.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.LongPredicate;
+
+/**
+ * A functional interface like {@link LongPredicate} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongPredicate<E extends Throwable> {
+
+ /** FALSE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongPredicate FALSE = t -> false;
+
+ /** TRUE singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongPredicate TRUE = t -> true;
+
+ /**
+ * Returns The FALSE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongPredicate<E> falsePredicate() {
+ return FALSE;
+ }
+
+ /**
+ * Returns The TRUE singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongPredicate<E> truePredicate() {
+ return TRUE;
+ }
+
+ /**
+ * Returns a composed {@link FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}.
+ *
+ * @param other a predicate that will be logically-ANDed with this predicate.
+ * @return a composed {@link FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableLongPredicate<E> and(final FailableLongPredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) && other.test(t);
+ }
+
+ /**
+ * Returns a predicate that negates this predicate.
+ *
+ * @return a predicate that negates this predicate.
+ */
+ default FailableLongPredicate<E> negate() {
+ return t -> !test(t);
+ }
+
+ /**
+ * Returns a composed {@link FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}.
+ *
+ * @param other a predicate that will be logically-ORed with this predicate.
+ * @return a composed {@link FailableLongPredicate} like {@link LongPredicate#and(LongPredicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailableLongPredicate<E> or(final FailableLongPredicate<E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) || other.test(t);
+ }
+
+ /**
+ * Tests the predicate.
+ *
+ * @param value the parameter for the predicate to accept.
+ * @return {@code true} if the input argument matches the predicate, {@code false} otherwise.
+ * @throws E Thrown when the consumer fails.
+ */
+ boolean test(long value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java
new file mode 100644
index 000000000..1b6ee197f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.LongSupplier;
+
+/**
+ * A functional interface like {@link LongSupplier} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongSupplier<E extends Throwable> {
+
+ /**
+ * Supplies a long.
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ long getAsLong() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java
new file mode 100644
index 000000000..e0105bb72
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongToDoubleFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.LongToDoubleFunction;
+
+/**
+ * A functional interface like {@link LongToDoubleFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongToDoubleFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongToDoubleFunction NOP = t -> 0d;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongToDoubleFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ double applyAsDouble(long value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java
new file mode 100644
index 000000000..e9a7a9efc
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongToIntFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.LongToIntFunction;
+
+/**
+ * A functional interface like {@link LongToIntFunction} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableLongToIntFunction<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongToIntFunction NOP = t -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongToIntFunction<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ int applyAsInt(long value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java b/src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java
new file mode 100644
index 000000000..b58f79ddf
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableLongUnaryOperator.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.LongUnaryOperator;
+
+/**
+ * A functional interface like {@link LongUnaryOperator} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+public interface FailableLongUnaryOperator<E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableLongUnaryOperator NOP = t -> 0L;
+
+ /**
+ * Returns a unary operator that always returns its input argument.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return a unary operator that always returns its input argument
+ */
+ static <E extends Throwable> FailableLongUnaryOperator<E> identity() {
+ return t -> t;
+ }
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <E extends Throwable> FailableLongUnaryOperator<E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Returns a composed {@link FailableDoubleUnaryOperator} like {@link LongUnaryOperator#andThen(LongUnaryOperator)}.
+ *
+ * @param after the operator to apply after this one.
+ * @return a composed {@link FailableLongUnaryOperator} like {@link LongUnaryOperator#andThen(LongUnaryOperator)}.
+ * @throws NullPointerException if after is null.
+ * @see #compose(FailableLongUnaryOperator)
+ */
+ default FailableLongUnaryOperator<E> andThen(final FailableLongUnaryOperator<E> after) {
+ Objects.requireNonNull(after);
+ return (final long t) -> after.applyAsLong(applyAsLong(t));
+ }
+
+ /**
+ * Applies this operator to the given operand.
+ *
+ * @param operand the operand
+ * @return the operator result
+ * @throws E Thrown when a consumer fails.
+ */
+ long applyAsLong(long operand) throws E;
+
+ /**
+ * Returns a composed {@link FailableLongUnaryOperator} like {@link LongUnaryOperator#compose(LongUnaryOperator)}.
+ *
+ * @param before the operator to apply before this one.
+ * @return a composed {@link FailableLongUnaryOperator} like {@link LongUnaryOperator#compose(LongUnaryOperator)}.
+ * @throws NullPointerException if before is null.
+ * @see #andThen(FailableLongUnaryOperator)
+ */
+ default FailableLongUnaryOperator<E> compose(final FailableLongUnaryOperator<E> before) {
+ Objects.requireNonNull(before);
+ return (final long v) -> applyAsLong(before.applyAsLong(v));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java
new file mode 100644
index 000000000..1c986b91f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableObjDoubleConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ObjDoubleConsumer;
+
+/**
+ * A functional interface like {@link ObjDoubleConsumer} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableObjDoubleConsumer<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableObjDoubleConsumer NOP = (t, u) -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableObjDoubleConsumer<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the object parameter for the consumable to accept.
+ * @param value the double parameter for the consumable to accept.
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(T object, double value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java
new file mode 100644
index 000000000..aa8c02cc1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableObjIntConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ObjIntConsumer;
+
+/**
+ * A functional interface like {@link ObjIntConsumer} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableObjIntConsumer<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableObjIntConsumer NOP = (t, u) -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableObjIntConsumer<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the object parameter for the consumable to accept.
+ * @param value the int parameter for the consumable to accept.
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(T object, int value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java b/src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java
new file mode 100644
index 000000000..b6e5a3552
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableObjLongConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ObjLongConsumer;
+
+/**
+ * A functional interface like {@link ObjLongConsumer} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableObjLongConsumer<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableObjLongConsumer NOP = (t, u) -> {/* NOP */};
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the object argument to the operation.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableObjLongConsumer<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Accepts the consumer.
+ *
+ * @param object the object parameter for the consumable to accept.
+ * @param value the long parameter for the consumable to accept.
+ * @throws E Thrown when the consumer fails.
+ */
+ void accept(T object, long value) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailablePredicate.java b/src/main/java/org/apache/commons/lang3/function/FailablePredicate.java
new file mode 100644
index 000000000..bf4a5e370
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailablePredicate.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * A functional interface like {@link Predicate} that declares a {@link Throwable}.
+ *
+ * @param <T> Predicate type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailablePredicate<T, E extends Throwable> {
+
+ /** FALSE singleton */
+ @SuppressWarnings("rawtypes")
+ FailablePredicate FALSE = t -> false;
+
+ /** TRUE singleton */
+ @SuppressWarnings("rawtypes")
+ FailablePredicate TRUE = t -> true;
+
+ /**
+ * Returns The FALSE singleton.
+ *
+ * @param <T> Predicate type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailablePredicate<T, E> falsePredicate() {
+ return FALSE;
+ }
+
+ /**
+ * Returns The TRUE singleton.
+ *
+ * @param <T> Predicate type.
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailablePredicate<T, E> truePredicate() {
+ return TRUE;
+ }
+
+ /**
+ * Returns a composed {@link FailablePredicate} like {@link Predicate#and(Predicate)}.
+ *
+ * @param other a predicate that will be logically-ANDed with this predicate.
+ * @return a composed {@link FailablePredicate} like {@link Predicate#and(Predicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailablePredicate<T, E> and(final FailablePredicate<? super T, E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) && other.test(t);
+ }
+
+ /**
+ * Returns a predicate that negates this predicate.
+ *
+ * @return a predicate that negates this predicate.
+ */
+ default FailablePredicate<T, E> negate() {
+ return t -> !test(t);
+ }
+
+ /**
+ * Returns a composed {@link FailablePredicate} like {@link Predicate#and(Predicate)}.
+ *
+ * @param other a predicate that will be logically-ORed with this predicate.
+ * @return a composed {@link FailablePredicate} like {@link Predicate#and(Predicate)}.
+ * @throws NullPointerException if other is null
+ */
+ default FailablePredicate<T, E> or(final FailablePredicate<? super T, E> other) {
+ Objects.requireNonNull(other);
+ return t -> test(t) || other.test(t);
+ }
+
+ /**
+ * Tests the predicate.
+ *
+ * @param object the object to test the predicate on
+ * @return the predicate's evaluation
+ * @throws E if the predicate fails
+ */
+ boolean test(T object) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableRunnable.java b/src/main/java/org/apache/commons/lang3/function/FailableRunnable.java
new file mode 100644
index 000000000..46103c9e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableRunnable.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+/**
+ * A functional interface like {@link Runnable} that declares a kind of {@link Throwable}.
+ *
+ * @param <E> The kind of {@link Throwable}.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableRunnable<E extends Throwable> {
+
+ /**
+ * Runs the function.
+ *
+ * @throws E Thrown when the function fails.
+ */
+ void run() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java
new file mode 100644
index 000000000..325e08705
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableShortSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.IntSupplier;
+
+/**
+ * A functional interface like {@link IntSupplier} but for {@code short} that declares a {@link Throwable}.
+ *
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.12.0
+ */
+@FunctionalInterface
+public interface FailableShortSupplier<E extends Throwable> {
+
+ /**
+ * Supplies an int.
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ short getAsShort() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableSupplier.java b/src/main/java/org/apache/commons/lang3/function/FailableSupplier.java
new file mode 100644
index 000000000..6f80af546
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableSupplier.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.Supplier;
+
+/**
+ * A functional interface like {@link Supplier} that declares a {@link Throwable}.
+ *
+ * @param <R> Return type.
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableSupplier<R, E extends Throwable> {
+
+ /**
+ * Supplies an object
+ *
+ * @return a result
+ * @throws E if the supplier fails
+ */
+ R get() throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java
new file mode 100644
index 000000000..aea48e716
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToDoubleBiFunction.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToDoubleBiFunction;
+
+/**
+ * A functional interface like {@link ToDoubleBiFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToDoubleBiFunction<T, U, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToDoubleBiFunction NOP = (t, u) -> 0d;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableToDoubleBiFunction<T, U, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @param u the second function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ double applyAsDouble(T t, U u) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java
new file mode 100644
index 000000000..b65591ca2
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToDoubleFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToDoubleFunction;
+
+/**
+ * A functional interface like {@link ToDoubleFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToDoubleFunction<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToDoubleFunction NOP = t -> 0d;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableToDoubleFunction<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ double applyAsDouble(T t) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java
new file mode 100644
index 000000000..67617134f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToIntBiFunction.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToIntBiFunction;
+
+/**
+ * A functional interface like {@link ToIntBiFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToIntBiFunction<T, U, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToIntBiFunction NOP = (t, u) -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableToIntBiFunction<T, U, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @param u the second function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ int applyAsInt(T t, U u) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java
new file mode 100644
index 000000000..711115754
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToIntFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToIntFunction;
+
+/**
+ * A functional interface like {@link ToIntFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToIntFunction<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToIntFunction NOP = t -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableToIntFunction<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ int applyAsInt(T t) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java
new file mode 100644
index 000000000..3c0d5f7ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToLongBiFunction.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToLongBiFunction;
+
+/**
+ * A functional interface like {@link ToLongBiFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToLongBiFunction<T, U, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToLongBiFunction NOP = (t, u) -> 0;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, U, E extends Throwable> FailableToLongBiFunction<T, U, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @param u the second function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ long applyAsLong(T t, U u) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java b/src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java
new file mode 100644
index 000000000..f093f7467
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/FailableToLongFunction.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.ToLongFunction;
+
+/**
+ * A functional interface like {@link ToLongFunction} that declares a {@link Throwable}.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @since 3.11
+ */
+@FunctionalInterface
+public interface FailableToLongFunction<T, E extends Throwable> {
+
+ /** NOP singleton */
+ @SuppressWarnings("rawtypes")
+ FailableToLongFunction NOP = t -> 0L;
+
+ /**
+ * Returns The NOP singleton.
+ *
+ * @param <T> the type of the argument to the function
+ * @param <E> The kind of thrown exception or error.
+ * @return The NOP singleton.
+ */
+ static <T, E extends Throwable> FailableToLongFunction<T, E> nop() {
+ return NOP;
+ }
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument
+ * @return the function result
+ * @throws E Thrown when the function fails.
+ */
+ long applyAsLong(T t) throws E;
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/IntToCharFunction.java b/src/main/java/org/apache/commons/lang3/function/IntToCharFunction.java
new file mode 100644
index 000000000..f3da38451
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/IntToCharFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.Function;
+
+/**
+ * Represents a function that accepts an int-valued argument and produces a long-valued result. This is the
+ * {@code int}-to-{@code long} primitive specialization for {@link Function}.
+ *
+ * <p>
+ * This is a <a href="package-summary.html">functional interface</a> whose functional method is
+ * {@link #applyAsChar(int)}.
+ * </p>
+ *
+ * @see Function
+ * @since 3.13.0
+ */
+@FunctionalInterface
+public interface IntToCharFunction {
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument.
+ * @return the function result.
+ */
+ char applyAsChar(int value);
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/MethodInvokers.java b/src/main/java/org/apache/commons/lang3/function/MethodInvokers.java
new file mode 100644
index 000000000..b90f0c14f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/MethodInvokers.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleProxies;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.exception.UncheckedIllegalAccessException;
+
+/**
+ * Converts {@link Method} objects to lambdas.
+ * <p>
+ * More specifically, produces instances of single-method interfaces which redirect calls to methods; see
+ * {@link #asInterfaceInstance(Class, Method)}.
+ * </p>
+ * <h2>Calling supplier methods with no arguments</h2>
+ * <p>
+ * If the interface's single-method defines no arguments, use {@link #asFunction(Method)} and then apply the function
+ * passing in the object to receive the method call.
+ * </p>
+ * <p>
+ * For example to invoke {@link String#length()}:
+ * </p>
+ *
+ * <pre>
+ * final Method method = String.class.getMethod("length");
+ * final Function&lt;String, Integer&gt; function = MethodInvokers.asFunction(method);
+ * assertEquals(3, function.apply("ABC"));
+ * </pre>
+ *
+ * <h2>Calling function methods with one argument</h2>
+ * <p>
+ * If the interface's single-method defines one argument, use {@link #asBiFunction(Method)} and then apply the function
+ * passing in the object to receive the method call. The second argument to the function is the only argument to the
+ * method.
+ * </p>
+ * <p>
+ * For example to invoke {@link String#charAt(int)}:
+ * </p>
+ *
+ * <pre>
+ * final Method method = String.class.getMethod("charAt", int.class);
+ * final BiFunction&lt;String, Integer, Character&gt; function = MethodInvokers.asBiFunction(method);
+ * assertEquals('C', function.apply("ABC", 2));
+ * </pre>
+ *
+ * @since 3.13.0
+ */
+public final class MethodInvokers {
+
+ /**
+ * Produces a {@link BiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as opposed
+ * to a fluent setter). You call the BiConsumer with two arguments: (1) the object receiving the method call, and (2)
+ * the method argument.
+ *
+ * @param <T> the type of the first argument to the operation: The type containing the Method.
+ * @param <U> the type of the second argument to the operation: The type of the method argument.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U> BiConsumer<T, U> asBiConsumer(final Method method) {
+ return asInterfaceInstance(BiConsumer.class, method);
+ }
+
+ /**
+ * Produces a {@link BiFunction} for a given a <em>function</em> Method. You call the BiFunction with two arguments: (1)
+ * the object receiving the method call, and (2) the method argument. The BiFunction return type must match the method's
+ * return type.
+ * <p>
+ * For example to invoke {@link String#charAt(int)}:
+ * </p>
+ *
+ * <pre>
+ * final Method method = String.class.getMethod("charAt", int.class);
+ * final BiFunction&lt;String, Integer, Character&gt; function = MethodInvokers.asBiFunction(method);
+ * assertEquals('C', function.apply("ABC", 2));
+ * </pre>
+ *
+ * @param <T> the type of the first argument to the function: The type containing the method.
+ * @param <U> the type of the second argument to the function: the method argument type.
+ * @param <R> the type of the result of the function: The method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U, R> BiFunction<T, U, R> asBiFunction(final Method method) {
+ return asInterfaceInstance(BiFunction.class, method);
+ }
+
+ /**
+ * Produces a {@link FailableBiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as
+ * opposed to a fluent setter). You call the FailableBiConsumer with two arguments: (1) the object receiving the method
+ * call, and (2) the method argument.
+ *
+ * @param <T> the type of the first argument to the operation: The type containing the Method.
+ * @param <U> the type of the second argument to the operation: The type of the method argument.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U> FailableBiConsumer<T, U, Throwable> asFailableBiConsumer(final Method method) {
+ return asInterfaceInstance(FailableBiConsumer.class, method);
+ }
+
+ /**
+ * Produces a {@link FailableBiFunction} for a given a <em>function</em> Method. You call the FailableBiFunction with
+ * two arguments: (1) the object receiving the method call, and (2) the method argument. The BiFunction return type must
+ * match the method's return type.
+ *
+ * @param <T> the type of the first argument to the function: The type containing the method.
+ * @param <U> the type of the second argument to the function: the method argument type.
+ * @param <R> the type of the result of the function: The method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U, R> FailableBiFunction<T, U, R, Throwable> asFailableBiFunction(final Method method) {
+ return asInterfaceInstance(FailableBiFunction.class, method);
+ }
+
+ /**
+ * Produces a {@link FailableFunction} for a given a <em>supplier</em> Method. You call the Function with one argument:
+ * the object receiving the method call. The FailableFunction return type must match the method's return type.
+ *
+ * @param <T> the type of the first argument to the function: The type containing the method.
+ * @param <R> the type of the result of the function: The method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, R> FailableFunction<T, R, Throwable> asFailableFunction(final Method method) {
+ return asInterfaceInstance(FailableFunction.class, method);
+ }
+
+ /**
+ * Produces a {@link FailableSupplier} for a given a <em>supplier</em> Method. The FailableSupplier return type must
+ * match the method's return type.
+ * <p>
+ * Only works with static methods.
+ * </p>
+ *
+ * @param <R> The Method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <R> FailableSupplier<R, Throwable> asFailableSupplier(final Method method) {
+ return asInterfaceInstance(FailableSupplier.class, method);
+ }
+
+ /**
+ * Produces a {@link Function} for a given a <em>supplier</em> Method. You call the Function with one argument: the
+ * object receiving the method call. The Function return type must match the method's return type.
+ * <p>
+ * For example to invoke {@link String#length()}:
+ * </p>
+ *
+ * <pre>
+ * final Method method = String.class.getMethod("length");
+ * final Function&lt;String, Integer&gt; function = MethodInvokers.asFunction(method);
+ * assertEquals(3, function.apply("ABC"));
+ * </pre>
+ *
+ * @param <T> the type of the first argument to the function: The type containing the method.
+ * @param <R> the type of the result of the function: The method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, R> Function<T, R> asFunction(final Method method) {
+ return asInterfaceInstance(Function.class, method);
+ }
+
+ /**
+ * Produces an instance of the given single-method interface which redirects its calls to the given method.
+ * <p>
+ * For the definition of "single-method", see {@linkplain MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)}.
+ * </p>
+ *
+ * @param <T> The interface type.
+ * @param interfaceClass a class object representing {@code T}.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ * @see MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)
+ */
+ public static <T> T asInterfaceInstance(final Class<T> interfaceClass, final Method method) {
+ return MethodHandleProxies.asInterfaceInstance(Objects.requireNonNull(interfaceClass, "interfaceClass"), unreflectUnchecked(method));
+ }
+
+ /**
+ * Produces a {@link Supplier} for a given a <em>supplier</em> Method. The Supplier return type must match the method's
+ * return type.
+ * <p>
+ * Only works with static methods.
+ * </p>
+ *
+ * @param <R> The Method return type.
+ * @param method the method to invoke.
+ * @return a correctly-typed wrapper for the given target.
+ */
+ @SuppressWarnings("unchecked")
+ public static <R> Supplier<R> asSupplier(final Method method) {
+ return asInterfaceInstance(Supplier.class, method);
+ }
+
+ /**
+ * Throws NullPointerException if {@code method} is {@code null}.
+ *
+ * @param method The method to test.
+ * @return The given method.
+ * @throws NullPointerException if {@code method} is {@code null}.
+ */
+ private static Method requireMethod(final Method method) {
+ return Objects.requireNonNull(method, "method");
+ }
+
+ private static MethodHandle unreflect(final Method method) throws IllegalAccessException {
+ return MethodHandles.lookup().unreflect(requireMethod(method));
+ }
+
+ private static MethodHandle unreflectUnchecked(final Method method) {
+ try {
+ return unreflect(method);
+ } catch (final IllegalAccessException e) {
+ throw new UncheckedIllegalAccessException(e);
+ }
+ }
+
+ /**
+ * No need to create instances.
+ */
+ private MethodInvokers() {
+ // noop
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/Suppliers.java b/src/main/java/org/apache/commons/lang3/function/Suppliers.java
new file mode 100644
index 000000000..6467a4780
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/Suppliers.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.Supplier;
+
+/**
+ * Helps use {@link Supplier}.
+ *
+ * @since 3.13.0
+ */
+public class Suppliers {
+
+ /**
+ * Null-safe call to {@link Supplier#get()}.
+ *
+ * @param <T> 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> T get(final Supplier<T> supplier) {
+ return supplier == null ? null : supplier.get();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java b/src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java
new file mode 100644
index 000000000..09bdb73eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/ToBooleanBiFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.function.BiFunction;
+
+/**
+ * A function that accepts two arguments and produces a boolean result. This is the {@code boolean}-producing primitive
+ * specialization for {@link BiFunction}.
+ *
+ * @param <T> the type of the first argument to the function.
+ * @param <U> the type of the second argument to the function.
+ *
+ * @see BiFunction
+ * @since 3.12.0
+ */
+@FunctionalInterface
+public interface ToBooleanBiFunction<T, U> {
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param t the first function argument.
+ * @param u the second function argument.
+ * @return the function result.
+ */
+ boolean applyAsBoolean(T t, U u);
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/TriConsumer.java b/src/main/java/org/apache/commons/lang3/function/TriConsumer.java
new file mode 100644
index 000000000..d0ba9b466
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/TriConsumer.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Represents an operation that accepts three input arguments and returns no result. This is the three-arity
+ * specialization of {@link Consumer}. Unlike most other functional interfaces, {@link TriConsumer} is expected to
+ * operate via side-effects.
+ *
+ * <p>
+ * This is a {@link FunctionalInterface} whose functional method is {@link #accept(Object, Object, Object)}.
+ * </p>
+ * <p>
+ * Provenance: Apache Log4j 2.7
+ * </p>
+ *
+ * @param <T> type of the first argument
+ * @param <U> type of the second argument
+ * @param <V> type of the third argument
+ * @since 3.13.0
+ */
+@FunctionalInterface
+public interface TriConsumer<T, U, V> {
+
+ /**
+ * Performs the operation given the specified arguments.
+ *
+ * @param k the first input argument
+ * @param v the second input argument
+ * @param s the third input argument
+ */
+ void accept(T k, U v, V s);
+
+ /**
+ * Returns a composed {@link TriConsumer} that performs, in sequence, this operation followed by the {@code after}
+ * operation. If performing either operation throws an exception, it is relayed to the caller of the composed
+ * operation. If performing this operation throws an exception, the {@code after} operation will not be performed.
+ *
+ * @param after the operation to perform after this operation.
+ * @return a composed {@link TriConsumer} that performs in sequence this operation followed by the {@code after}
+ * operation.
+ * @throws NullPointerException if {@code after} is null.
+ */
+ default TriConsumer<T, U, V> andThen(final TriConsumer<? super T, ? super U, ? super V> after) {
+ Objects.requireNonNull(after);
+
+ return (t, u, v) -> {
+ accept(t, u, v);
+ after.accept(t, u, v);
+ };
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/TriFunction.java b/src/main/java/org/apache/commons/lang3/function/TriFunction.java
new file mode 100644
index 000000000..7d8b81c50
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/TriFunction.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.function;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents a function that accepts three arguments and produces a result. This is the three-arity specialization of
+ * {@link Function}.
+ *
+ * <p>
+ * This is a <a href="package-summary.html">functional interface</a> whose functional method is
+ * {@link #apply(Object, Object, Object)}.
+ * </p>
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <V> the type of the third argument to the function
+ * @param <R> the type of the result of the function
+ *
+ * @see Function
+ * @since 3.12.0
+ */
+@FunctionalInterface
+public interface TriFunction<T, U, V, R> {
+
+ /**
+ * 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
+ * composed function.
+ *
+ * @param <W> the type of output of the {@code after} function, and of the composed function
+ * @param after the function to apply after this function is applied
+ * @return a composed function that first applies this function and then applies the {@code after} function
+ * @throws NullPointerException if after is null
+ */
+ default <W> TriFunction<T, U, V, W> andThen(final Function<? super R, ? extends W> after) {
+ Objects.requireNonNull(after);
+ return (final T t, final U u, final V v) -> after.apply(apply(t, u, v));
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/function/package-info.java b/src/main/java/org/apache/commons/lang3/function/package-info.java
new file mode 100644
index 000000000..330e7c78d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/function/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides functional interfaces to complement those in {@code java.lang.function} and utilities for working with Java
+ * 8 lambdas.
+ *
+ * <p>
+ * Contains failable functional interfaces that address the fact that lambdas are supposed not to throw Exceptions, at
+ * least not checked Exceptions, A.K.A. instances of {@link java.lang.Exception}. A failable functional interface
+ * declares a type of Exception that may be raised if the function fails.
+ * </p>
+ *
+ * @since 3.11
+ */
+package org.apache.commons.lang3.function;
diff --git a/src/main/java/org/apache/commons/lang3/math/Fraction.java b/src/main/java/org/apache/commons/lang3/math/Fraction.java
new file mode 100644
index 000000000..fe1ae846b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/math/Fraction.java
@@ -0,0 +1,908 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.math;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+/**
+ * {@link Fraction} is a {@link Number} implementation that
+ * stores fractions accurately.
+ *
+ * <p>This class is immutable, and interoperable with most methods that accept
+ * a {@link Number}.</p>
+ *
+ * <p>Note that this class is intended for common use cases, it is <i>int</i>
+ * based and thus suffers from various overflow issues. For a BigInteger based
+ * equivalent, please see the Commons Math BigFraction class.</p>
+ *
+ * @since 2.0
+ */
+public final class Fraction extends Number implements Comparable<Fraction> {
+
+ /**
+ * Required for serialization support. Lang version 2.0.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 65382027393090L;
+
+ /**
+ * {@link Fraction} representation of 0.
+ */
+ public static final Fraction ZERO = new Fraction(0, 1);
+ /**
+ * {@link Fraction} representation of 1.
+ */
+ public static final Fraction ONE = new Fraction(1, 1);
+ /**
+ * {@link Fraction} representation of 1/2.
+ */
+ public static final Fraction ONE_HALF = new Fraction(1, 2);
+ /**
+ * {@link Fraction} representation of 1/3.
+ */
+ public static final Fraction ONE_THIRD = new Fraction(1, 3);
+ /**
+ * {@link Fraction} representation of 2/3.
+ */
+ public static final Fraction TWO_THIRDS = new Fraction(2, 3);
+ /**
+ * {@link Fraction} representation of 1/4.
+ */
+ public static final Fraction ONE_QUARTER = new Fraction(1, 4);
+ /**
+ * {@link Fraction} representation of 2/4.
+ */
+ public static final Fraction TWO_QUARTERS = new Fraction(2, 4);
+ /**
+ * {@link Fraction} representation of 3/4.
+ */
+ public static final Fraction THREE_QUARTERS = new Fraction(3, 4);
+ /**
+ * {@link Fraction} representation of 1/5.
+ */
+ public static final Fraction ONE_FIFTH = new Fraction(1, 5);
+ /**
+ * {@link Fraction} representation of 2/5.
+ */
+ public static final Fraction TWO_FIFTHS = new Fraction(2, 5);
+ /**
+ * {@link Fraction} representation of 3/5.
+ */
+ public static final Fraction THREE_FIFTHS = new Fraction(3, 5);
+ /**
+ * {@link Fraction} representation of 4/5.
+ */
+ public static final Fraction FOUR_FIFTHS = new Fraction(4, 5);
+
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Creates a {@link Fraction} instance with the 2 parts
+ * of a fraction Y/Z.
+ *
+ * <p>Any negative signs are resolved to be on the numerator.</p>
+ *
+ * @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.
+ *
+ * <p>The negative sign must be passed in on the whole number part.</p>
+ *
+ * @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.
+ *
+ * <p>For example, if the input parameters represent 2/4, then the created
+ * fraction will be 1/2.</p>
+ *
+ * <p>Any negative signs are resolved to be on the numerator.</p>
+ *
+ * @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.
+ *
+ * <p>This method uses the <a href="https://web.archive.org/web/20210516065058/http%3A//archives.math.utk.edu/articles/atuyl/confrac/">
+ * continued fraction algorithm</a>, computing a maximum of
+ * 25 convergents and bounding the denominator by 10,000.</p>
+ *
+ * @param value the double value to convert
+ * @return a new fraction instance that is close to the value
+ * @throws ArithmeticException if {@code |value| &gt; Integer.MAX_VALUE}
+ * or {@code value = NaN}
+ * @throws ArithmeticException if the calculated denominator is {@code zero}
+ * @throws ArithmeticException if the algorithm does not converge
+ */
+ public static Fraction getFraction(double value) {
+ final int sign = value < 0 ? -1 : 1;
+ value = Math.abs(value);
+ if (value > Integer.MAX_VALUE || Double.isNaN(value)) {
+ throw new ArithmeticException("The value must not be greater than Integer.MAX_VALUE or NaN");
+ }
+ final int wholeNumber = (int) value;
+ value -= wholeNumber;
+
+ int numer0 = 0; // the pre-previous
+ int denom0 = 1; // the pre-previous
+ int numer1 = 1; // the previous
+ int denom1 = 0; // the previous
+ int numer2; // the current, setup in calculation
+ int denom2; // the current, setup in calculation
+ int a1 = (int) value;
+ int a2;
+ double x1 = 1;
+ double x2;
+ double y1 = value - a1;
+ double y2;
+ double delta1, delta2 = Double.MAX_VALUE;
+ double fraction;
+ int i = 1;
+ do {
+ delta1 = delta2;
+ a2 = (int) (x1 / y1);
+ x2 = y1;
+ y2 = x1 - a2 * y1;
+ numer2 = a1 * numer1 + numer0;
+ denom2 = a1 * denom1 + denom0;
+ fraction = (double) numer2 / (double) denom2;
+ delta2 = Math.abs(value - fraction);
+ a1 = a2;
+ x1 = x2;
+ y1 = y2;
+ numer0 = numer1;
+ denom0 = denom1;
+ numer1 = numer2;
+ denom1 = denom2;
+ i++;
+ } while (delta1 > delta2 && denom2 <= 10000 && denom2 > 0 && i < 25);
+ if (i == 25) {
+ throw new ArithmeticException("Unable to convert double to fraction");
+ }
+ return getReducedFraction((numer0 + wholeNumber * denom0) * sign, denom0);
+ }
+
+ /**
+ * Creates a Fraction from a {@link String}.
+ *
+ * <p>The formats accepted are:</p>
+ *
+ * <ol>
+ * <li>{@code double} String containing a dot</li>
+ * <li>'X Y/Z'</li>
+ * <li>'Y/Z'</li>
+ * <li>'X' (a simple whole number)</li>
+ * </ol>
+ * <p>and a .</p>
+ *
+ * @param str the string to parse, must not be {@code null}
+ * @return the new {@link Fraction} instance
+ * @throws NullPointerException if the string is {@code null}
+ * @throws NumberFormatException if the number format is invalid
+ */
+ public static Fraction getFraction(String str) {
+ Objects.requireNonNull(str, "str");
+ // parse double format
+ int pos = str.indexOf('.');
+ if (pos >= 0) {
+ return getFraction(Double.parseDouble(str));
+ }
+
+ // parse X Y/Z format
+ pos = str.indexOf(' ');
+ if (pos > 0) {
+ final int whole = Integer.parseInt(str.substring(0, pos));
+ str = str.substring(pos + 1);
+ pos = str.indexOf('/');
+ if (pos < 0) {
+ throw new NumberFormatException("The fraction could not be parsed as the format X Y/Z");
+ }
+ final int numer = Integer.parseInt(str.substring(0, pos));
+ final int denom = Integer.parseInt(str.substring(pos + 1));
+ return getFraction(whole, numer, denom);
+ }
+
+ // parse Y/Z format
+ pos = str.indexOf('/');
+ if (pos < 0) {
+ // simple whole number
+ return getFraction(Integer.parseInt(str), 1);
+ }
+ final int numer = Integer.parseInt(str.substring(0, pos));
+ final int denom = Integer.parseInt(str.substring(pos + 1));
+ return getFraction(numer, denom);
+ }
+
+ /**
+ * Gets the numerator part of the fraction.
+ *
+ * <p>This method may return a value greater than the denominator, an
+ * improper fraction, such as the seven in 7/4.</p>
+ *
+ * @return the numerator fraction part
+ */
+ 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.
+ *
+ * <p>An improper fraction 7/4 can be resolved into a proper one, 1 3/4.
+ * This method returns the 3 from the proper fraction.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>An improper fraction 7/4 can be resolved into a proper one, 1 3/4.
+ * This method returns the 1 from the proper fraction.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>For example, if this fraction represents 2/4, then the result
+ * will be 1/2.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * Gets a fraction that is the inverse (1/fraction) of this one.
+ *
+ * <p>The returned fraction is not reduced.</p>
+ *
+ * @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.
+ *
+ * <p>The returned fraction is not reduced.</p>
+ *
+ * @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.
+ * <p>More precisely: {@code (fraction &gt;= 0 ? this : -fraction)}</p>
+ *
+ * <p>The returned fraction is not reduced.</p>
+ *
+ * @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.
+ *
+ * <p>The returned fraction is in reduced form.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * Gets the greatest common divisor of the absolute value of
+ * two numbers, using the "binary gcd" method which avoids
+ * division and modulo operations. See Knuth 4.5.2 algorithm B.
+ * This algorithm is due to Josef Stein (1961).
+ *
+ * @param u a non-zero number
+ * @param v a non-zero number
+ * @return the greatest common divisor, never zero
+ */
+ private static int greatestCommonDivisor(int u, int v) {
+ // From Commons Math:
+ if (u == 0 || v == 0) {
+ if (u == Integer.MIN_VALUE || v == Integer.MIN_VALUE) {
+ throw new ArithmeticException("overflow: gcd is 2^31");
+ }
+ return Math.abs(u) + Math.abs(v);
+ }
+ // if either operand is abs 1, return 1:
+ if (Math.abs(u) == 1 || Math.abs(v) == 1) {
+ return 1;
+ }
+ // keep u and v negative, as negative integers range down to
+ // -2^31, while positive numbers can only be as large as 2^31-1
+ // (i.e. we can't necessarily negate a negative number without
+ // overflow)
+ if (u > 0) {
+ u = -u;
+ } // make u negative
+ if (v > 0) {
+ v = -v;
+ } // make v negative
+ // B1. [Find power of 2]
+ int k = 0;
+ while ((u & 1) == 0 && (v & 1) == 0 && k < 31) { // while u and v are both even...
+ u /= 2;
+ v /= 2;
+ k++; // cast out twos.
+ }
+ if (k == 31) {
+ throw new ArithmeticException("overflow: gcd is 2^31");
+ }
+ // B2. Initialize: u and v have been divided by 2^k and at least
+ // one is odd.
+ int t = (u & 1) == 1 ? v : -(u / 2)/* B3 */;
+ // t negative: u was odd, v may be even (t replaces v)
+ // t positive: u was even, v is odd (t replaces u)
+ do {
+ /* assert u<0 && v<0; */
+ // B4/B3: cast out twos from t.
+ while ((t & 1) == 0) { // while t is even.
+ t /= 2; // cast out twos
+ }
+ // B5 [reset max(u,v)]
+ if (t > 0) {
+ u = -t;
+ } else {
+ v = t;
+ }
+ // B6/B3. at this point both u and v should be odd.
+ t = (v - u) / 2;
+ // |u| larger: t positive (replace u)
+ // |v| larger: t negative (replace v)
+ } while (t != 0);
+ return -u * (1 << k); // gcd is u*2^k
+ }
+
+ /**
+ * Multiply two integers, checking for overflow.
+ *
+ * @param x a factor
+ * @param y a factor
+ * @return the product {@code x*y}
+ * @throws ArithmeticException if the result can not be represented as
+ * an int
+ */
+ private static int mulAndCheck(final int x, final int y) {
+ final long m = (long) x * (long) y;
+ if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
+ throw new ArithmeticException("overflow: mul");
+ }
+ return (int) m;
+ }
+
+ /**
+ * Multiply two non-negative integers, checking for overflow.
+ *
+ * @param x a non-negative factor
+ * @param y a non-negative factor
+ * @return the product {@code x*y}
+ * @throws ArithmeticException if the result can not be represented as
+ * an int
+ */
+ private static int mulPosAndCheck(final int x, final int y) {
+ /* assert x>=0 && y>=0; */
+ final long m = (long) x * (long) y;
+ if (m > Integer.MAX_VALUE) {
+ throw new ArithmeticException("overflow: mulPos");
+ }
+ 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.
+ *
+ * @param x the minuend
+ * @param y the subtrahend
+ * @return the difference {@code x-y}
+ * @throws ArithmeticException if the result can not be represented as
+ * an int
+ */
+ private static int subAndCheck(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;
+ }
+
+ /**
+ * Adds the value of this fraction to another, returning the result in reduced form.
+ * The algorithm follows Knuth, 4.5.1.
+ *
+ * @param fraction the fraction to add, must not be {@code null}
+ * @return a {@link Fraction} instance with the resulting values
+ * @throws IllegalArgumentException if the fraction is {@code null}
+ * @throws ArithmeticException if the resulting numerator or denominator exceeds
+ * {@code Integer.MAX_VALUE}
+ */
+ public Fraction add(final Fraction fraction) {
+ 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 IllegalArgumentException 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.
+ *
+ * @param fraction the fraction to subtract, must not be {@code null}
+ * @param isAdd true to add, false to subtract
+ * @return a {@link Fraction} instance with the resulting values
+ * @throws IllegalArgumentException if the fraction is {@code null}
+ * @throws ArithmeticException if the resulting numerator or denominator
+ * cannot be represented in an {@code int}.
+ */
+ private Fraction addSub(final Fraction fraction, final boolean isAdd) {
+ Objects.requireNonNull(fraction, "fraction");
+ // zero is identity for addition.
+ if (numerator == 0) {
+ return isAdd ? fraction : fraction.negate();
+ }
+ if (fraction.numerator == 0) {
+ return this;
+ }
+ // if denominators are randomly distributed, d1 will be 1 about 61%
+ // of the time.
+ final int d1 = greatestCommonDivisor(denominator, fraction.denominator);
+ if (d1 == 1) {
+ // result is ( (u*v' +/- u'v) / u'v')
+ final int uvp = mulAndCheck(numerator, fraction.denominator);
+ final int upv = mulAndCheck(fraction.numerator, denominator);
+ return new Fraction(isAdd ? addAndCheck(uvp, upv) : subAndCheck(uvp, upv), mulPosAndCheck(denominator,
+ fraction.denominator));
+ }
+ // the quantity 't' requires 65 bits of precision; see knuth 4.5.1
+ // exercise 7. we're going to use a BigInteger.
+ // t = u(v'/d1) +/- v(u'/d1)
+ final BigInteger uvp = BigInteger.valueOf(numerator).multiply(BigInteger.valueOf(fraction.denominator / d1));
+ final BigInteger upv = BigInteger.valueOf(fraction.numerator).multiply(BigInteger.valueOf(denominator / d1));
+ final BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv);
+ // but d2 doesn't need extra precision because
+ // d2 = gcd(t,d1) = gcd(t mod d1, d1)
+ final int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue();
+ final int d2 = tmodd1 == 0 ? d1 : greatestCommonDivisor(tmodd1, d1);
+
+ // result is (t/d2) / (u'/d1)(v'/d2)
+ final BigInteger w = t.divide(BigInteger.valueOf(d2));
+ if (w.bitLength() > 31) {
+ throw new ArithmeticException("overflow: numerator too large after multiply");
+ }
+ 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..
+ *
+ * <p>To be equal, both values must be equal. Thus 2/4 is not equal to 1/2.</p>
+ *
+ * @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.
+ *
+ * <p>Note: this class has a natural ordering that is inconsistent
+ * with equals, because, for example, equals treats 1/2 and 2/4 as
+ * different, whereas compareTo treats them as equal.
+ *
+ * @param other the object to compare to
+ * @return -1 if this is less, 0 if equal, +1 if greater
+ * @throws ClassCastException if the object is not a {@link Fraction}
+ * @throws NullPointerException if the object is {@code null}
+ */
+ @Override
+ public int compareTo(final Fraction other) {
+ if (this == other) {
+ return 0;
+ }
+ if (numerator == other.numerator && denominator == other.denominator) {
+ return 0;
+ }
+
+ // otherwise see which is less
+ final long first = (long) numerator * (long) other.denominator;
+ final long second = (long) other.numerator * (long) denominator;
+ return Long.compare(first, second);
+ }
+
+ /**
+ * Gets the fraction as a {@link String}.
+ *
+ * <p>The format used is '<i>numerator</i>/<i>denominator</i>' always.
+ *
+ * @return a {@link String} form of the fraction
+ */
+ @Override
+ public String toString() {
+ if (toString == null) {
+ toString = getNumerator() + "/" + getDenominator();
+ }
+ return toString;
+ }
+
+ /**
+ * Gets the fraction as a proper {@link String} in the format X Y/Z.
+ *
+ * <p>The format used in '<i>wholeNumber</i> <i>numerator</i>/<i>denominator</i>'.
+ * If the whole number is zero it will be omitted. If the numerator is zero,
+ * only the whole number is returned.</p>
+ *
+ * @return a {@link String} form of the fraction
+ */
+ public String toProperString() {
+ if (toProperString == null) {
+ if (numerator == 0) {
+ toProperString = "0";
+ } else if (numerator == denominator) {
+ toProperString = "1";
+ } else if (numerator == -1 * denominator) {
+ toProperString = "-1";
+ } else if ((numerator > 0 ? -numerator : numerator) < -denominator) {
+ // note that we do the magnitude comparison test above with
+ // NEGATIVE (not positive) numbers, since negative numbers
+ // have a larger range. otherwise numerator==Integer.MIN_VALUE
+ // is handled incorrectly.
+ final int properNumerator = getProperNumerator();
+ if (properNumerator == 0) {
+ toProperString = Integer.toString(getProperWhole());
+ } else {
+ toProperString = getProperWhole() + " " + properNumerator + "/" + getDenominator();
+ }
+ } else {
+ toProperString = getNumerator() + "/" + getDenominator();
+ }
+ }
+ return toProperString;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java b/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java
new file mode 100644
index 000000000..04dd0e33a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/math/IEEE754rUtils.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.math;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Provides IEEE-754r variants of NumberUtils methods.
+ *
+ * <p>See: <a href="https://en.wikipedia.org/wiki/IEEE_754r">https://en.wikipedia.org/wiki/IEEE_754r</a></p>
+ *
+ * @since 2.4
+ */
+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.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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
+ * @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 max(double[]) to max(double...)
+ */
+ public static double max(final double... array) {
+ Objects.requireNonNull(array, "array");
+ Validate.isTrue(array.length != 0, "Array cannot be empty.");
+
+ // Finds and returns max
+ double max = array[0];
+ for (int j = 1; j < array.length; j++) {
+ max = max(array[j], max);
+ }
+
+ return max;
+ }
+
+ /**
+ * Returns the maximum 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 max(float[]) to max(float...)
+ */
+ public static float max(final float... array) {
+ Objects.requireNonNull(array, "array");
+ Validate.isTrue(array.length != 0, "Array cannot be empty.");
+
+ // Finds and returns max
+ float max = array[0];
+ for (int j = 1; j < array.length; j++) {
+ max = max(array[j], max);
+ }
+
+ return max;
+ }
+
+ /**
+ * Gets the maximum of three {@code double} values.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @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 float} values.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @param a value 1
+ * @param b value 2
+ * @param c value 3
+ * @return the largest of the values
+ */
+ public static float max(final float a, final float b, final float c) {
+ return max(max(a, b), c);
+ }
+
+ /**
+ * Gets the maximum of two {@code float} values.
+ *
+ * <p>NaN is only returned if all numbers are NaN as per IEEE-754r.</p>
+ *
+ * @param a value 1
+ * @param b value 2
+ * @return the largest of the values
+ */
+ public static float max(final float a, final float b) {
+ if (Float.isNaN(a)) {
+ return b;
+ }
+ if (Float.isNaN(b)) {
+ return a;
+ }
+ return Math.max(a, b);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java
new file mode 100644
index 000000000..02a6069b3
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java
@@ -0,0 +1,1850 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.math;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Provides extra functionality for Java Number classes.
+ *
+ * @since 2.0
+ */
+public class NumberUtils {
+
+ /** Reusable Long constant for zero. */
+ public static final Long LONG_ZERO = Long.valueOf(0L);
+ /** Reusable Long constant for one. */
+ public static final Long LONG_ONE = Long.valueOf(1L);
+ /** Reusable Long constant for minus one. */
+ public static final Long LONG_MINUS_ONE = Long.valueOf(-1L);
+ /** Reusable Integer constant for zero. */
+ public static final Integer INTEGER_ZERO = Integer.valueOf(0);
+ /** Reusable Integer constant for one. */
+ public static final Integer INTEGER_ONE = Integer.valueOf(1);
+ /** Reusable Integer constant for two */
+ public static final Integer INTEGER_TWO = Integer.valueOf(2);
+ /** Reusable Integer constant for minus one. */
+ public static final Integer INTEGER_MINUS_ONE = Integer.valueOf(-1);
+ /** Reusable Short constant for zero. */
+ public static final Short SHORT_ZERO = Short.valueOf((short) 0);
+ /** Reusable Short constant for one. */
+ public static final Short SHORT_ONE = Short.valueOf((short) 1);
+ /** Reusable Short constant for minus one. */
+ public static final Short SHORT_MINUS_ONE = Short.valueOf((short) -1);
+ /** Reusable Byte constant for zero. */
+ public static final Byte BYTE_ZERO = Byte.valueOf((byte) 0);
+ /** Reusable Byte constant for one. */
+ public static final Byte BYTE_ONE = Byte.valueOf((byte) 1);
+ /** Reusable Byte constant for minus one. */
+ public static final Byte BYTE_MINUS_ONE = Byte.valueOf((byte) -1);
+ /** Reusable Double constant for zero. */
+ public static final Double DOUBLE_ZERO = Double.valueOf(0.0d);
+ /** Reusable Double constant for one. */
+ public static final Double DOUBLE_ONE = Double.valueOf(1.0d);
+ /** Reusable Double constant for minus one. */
+ public static final Double DOUBLE_MINUS_ONE = Double.valueOf(-1.0d);
+ /** Reusable Float constant for zero. */
+ public static final Float FLOAT_ZERO = Float.valueOf(0.0f);
+ /** Reusable Float constant for one. */
+ public static final Float FLOAT_ONE = Float.valueOf(1.0f);
+ /** Reusable Float constant for minus one. */
+ public static final Float FLOAT_MINUS_ONE = Float.valueOf(-1.0f);
+
+ /**
+ * {@link Integer#MAX_VALUE} as a {@link Long}.
+ *
+ * @since 3.12.0
+ */
+ public static final Long LONG_INT_MAX_VALUE = Long.valueOf(Integer.MAX_VALUE);
+
+ /**
+ * {@link Integer#MIN_VALUE} as a {@link Long}.
+ *
+ * @since 3.12.0
+ */
+ public static final Long LONG_INT_MIN_VALUE = Long.valueOf(Integer.MIN_VALUE);
+
+
+ /**
+ * {@link NumberUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as {@code NumberUtils.toInt("6");}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public NumberUtils() {
+ }
+
+ /**
+ * Convert a {@link String} to an {@code int}, returning
+ * {@code zero} if the conversion fails.
+ *
+ * <p>If the string is {@code null}, {@code zero} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toInt(null) = 0
+ * NumberUtils.toInt("") = 0
+ * NumberUtils.toInt("1") = 1
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toInt(null, 1) = 1
+ * NumberUtils.toInt("", 1) = 1
+ * NumberUtils.toInt("1", 0) = 1
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, {@code zero} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toLong(null) = 0L
+ * NumberUtils.toLong("") = 0L
+ * NumberUtils.toLong("1") = 1L
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toLong(null, 1L) = 1L
+ * NumberUtils.toLong("", 1L) = 1L
+ * NumberUtils.toLong("1", 0L) = 1L
+ * </pre>
+ *
+ * @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 String} to a {@code float}, returning
+ * {@code 0.0f} if the conversion fails.
+ *
+ * <p>If the string {@code str} is {@code null},
+ * {@code 0.0f} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toFloat(null) = 0.0f
+ * NumberUtils.toFloat("") = 0.0f
+ * NumberUtils.toFloat("1.5") = 1.5f
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string {@code str} is {@code null}, the default
+ * value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toFloat(null, 1.1f) = 1.0f
+ * NumberUtils.toFloat("", 1.1f) = 1.1f
+ * NumberUtils.toFloat("1.5", 0.0f) = 1.5f
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string {@code str} is {@code null},
+ * {@code 0.0d} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toDouble(null) = 0.0d
+ * NumberUtils.toDouble("") = 0.0d
+ * NumberUtils.toDouble("1.5") = 1.5d
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string {@code str} is {@code null}, the default
+ * value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toDouble(null, 1.1d) = 1.1d
+ * NumberUtils.toDouble("", 1.1d) = 1.1d
+ * NumberUtils.toDouble("1.5", 0.0d) = 1.5d
+ * </pre>
+ *
+ * @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}.
+ *
+ * <p>If the {@link BigDecimal} {@code value} is
+ * {@code null}, then the specified default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toDouble(null) = 0.0d
+ * NumberUtils.toDouble(BigDecimal.valueOf(8.5d)) = 8.5d
+ * </pre>
+ *
+ * @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}.
+ *
+ * <p>If the {@link BigDecimal} {@code value} is
+ * {@code null}, then the specified default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toDouble(null, 1.1d) = 1.1d
+ * NumberUtils.toDouble(BigDecimal.valueOf(8.5d), 1.1d) = 8.5d
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, {@code zero} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toByte(null) = 0
+ * NumberUtils.toByte("") = 0
+ * NumberUtils.toByte("1") = 1
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toByte(null, 1) = 1
+ * NumberUtils.toByte("", 1) = 1
+ * NumberUtils.toByte("1", 0) = 1
+ * </pre>
+ *
+ * @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 String} to a {@code short}, returning
+ * {@code zero} if the conversion fails.
+ *
+ * <p>If the string is {@code null}, {@code zero} is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toShort(null) = 0
+ * NumberUtils.toShort("") = 0
+ * NumberUtils.toShort("1") = 1
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>If the string is {@code null}, the default value is returned.</p>
+ *
+ * <pre>
+ * NumberUtils.toShort(null, 1) = 1
+ * NumberUtils.toShort("", 1) = 1
+ * NumberUtils.toShort("1", 0) = 1
+ * </pre>
+ *
+ * @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;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <p>Note, the scale of a {@link BigDecimal} is the number of digits to the right of the
+ * decimal point.</p>
+ *
+ * @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 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.
+ *
+ * <p>Note, the scale of a {@link BigDecimal} is the number of digits to the right of the
+ * decimal point.</p>
+ *
+ * @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.
+ *
+ * <p>Note, the scale of a {@link BigDecimal} is the number of digits to the right of the
+ * decimal point.</p>
+ *
+ * @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.
+ *
+ * <p>Note, the scale of a {@link BigDecimal} is the number of digits to the right of the
+ * decimal point.</p>
+ *
+ * @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.
+ *
+ * <p>If the string starts with {@code 0x} or {@code -0x} (lower or upper case) or {@code #} or {@code -#}, it
+ * will be interpreted as a hexadecimal Integer - or Long, if the number of digits after the
+ * prefix is more than 8 - or BigInteger if there are more than 16 digits.
+ * </p>
+ * <p>Then, the value is examined for a type qualifier on the end, i.e. one of
+ * {@code 'f', 'F', 'd', 'D', 'l', 'L'}. If it is found, it starts
+ * trying to create successively larger types from the type specified
+ * until one is found that can represent the value.</p>
+ *
+ * <p>If a type specifier is not found, it will check for a decimal point
+ * and then try successively larger types from {@link Integer} to
+ * {@link BigInteger} and from {@link Float} to
+ * {@link BigDecimal}.</p>
+ *
+ * <p>
+ * Integral values with a leading {@code 0} will be interpreted as octal; the returned number will
+ * be Integer, Long or BigDecimal as appropriate.
+ * </p>
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * <p>This method does not trim the input string, i.e., strings with leading
+ * or trailing spaces will generate NumberFormatExceptions.</p>
+ *
+ * @param str String containing a number, may be null
+ * @return Number created from the string (or null if the input is null)
+ * @throws NumberFormatException if the value cannot be converted
+ */
+ public static Number createNumber(final String str) {
+ if (str == null) {
+ return null;
+ }
+ if (StringUtils.isBlank(str)) {
+ throw new NumberFormatException("A blank string is not a valid number");
+ }
+ // Need to deal with all possible hex prefixes here
+ final String[] hex_prefixes = {"0x", "0X", "#"};
+ final int length = str.length();
+ final int offset = str.charAt(0) == '+' || str.charAt(0) == '-' ? 1 : 0;
+ int pfxLen = 0;
+ for (final String pfx : hex_prefixes) {
+ if (str.startsWith(pfx, offset)) {
+ pfxLen += pfx.length() + offset;
+ break;
+ }
+ }
+ if (pfxLen > 0) { // we have a hex number
+ char firstSigDigit = 0; // strip leading zeroes
+ for (int i = pfxLen; i < length; i++) {
+ firstSigDigit = str.charAt(i);
+ if (firstSigDigit != '0') {
+ break;
+ }
+ pfxLen++;
+ }
+ final int hexDigits = length - pfxLen;
+ if (hexDigits > 16 || hexDigits == 16 && firstSigDigit > '7') { // too many for Long
+ return createBigInteger(str);
+ }
+ if (hexDigits > 8 || hexDigits == 8 && firstSigDigit > '7') { // too many for an int
+ return createLong(str);
+ }
+ return createInteger(str);
+ }
+ final char lastChar = str.charAt(length - 1);
+ final String mant;
+ final String dec;
+ final String exp;
+ final int decPos = str.indexOf('.');
+ final int expPos = str.indexOf('e') + str.indexOf('E') + 1; // assumes both not present
+ // if both e and E are present, this is caught by the checks on expPos (which prevent IOOBE)
+ // and the parsing which will detect if e or E appear in a number due to using the wrong offset
+
+ // Detect if the return type has been requested
+ final boolean requestType = !Character.isDigit(lastChar) && lastChar != '.';
+ if (decPos > -1) { // there is a decimal point
+ if (expPos > -1) { // there is an exponent
+ if (expPos < decPos || expPos > length) { // prevents double exponent causing IOOBE
+ throw new NumberFormatException(str + " is not a valid number.");
+ }
+ dec = str.substring(decPos + 1, expPos);
+ } else {
+ // No exponent, but there may be a type character to remove
+ dec = str.substring(decPos + 1, requestType ? length - 1 : length);
+ }
+ mant = getMantissa(str, decPos);
+ } else {
+ if (expPos > -1) {
+ if (expPos > length) { // prevents double exponent causing IOOBE
+ throw new NumberFormatException(str + " is not a valid number.");
+ }
+ mant = getMantissa(str, expPos);
+ } else {
+ // No decimal, no exponent, but there may be a type character to remove
+ mant = getMantissa(str, requestType ? length - 1 : length);
+ }
+ dec = null;
+ }
+ if (requestType) {
+ if (expPos > -1 && expPos < length - 1) {
+ exp = str.substring(expPos + 1, length - 1);
+ } else {
+ exp = null;
+ }
+ //Requesting a specific type.
+ final String numeric = str.substring(0, length - 1);
+ switch (lastChar) {
+ case 'l' :
+ case 'L' :
+ if (dec == null
+ && exp == null
+ && (!numeric.isEmpty() && numeric.charAt(0) == '-' && isDigits(numeric.substring(1)) || isDigits(numeric))) {
+ try {
+ return createLong(numeric);
+ } catch (final NumberFormatException ignored) {
+ // Too big for a long
+ }
+ return createBigInteger(numeric);
+
+ }
+ throw new NumberFormatException(str + " is not a valid number.");
+ case 'f' :
+ case 'F' :
+ try {
+ final Float f = createFloat(str);
+ if (!(f.isInfinite() || f.floatValue() == 0.0F && !isZero(mant, dec))) {
+ //If it's too big for a float or the float value = 0 and the string
+ //has non-zeros in it, then float does not have the precision we want
+ return f;
+ }
+
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ //$FALL-THROUGH$
+ case 'd' :
+ case 'D' :
+ try {
+ final Double d = createDouble(str);
+ if (!(d.isInfinite() || d.doubleValue() == 0.0D && !isZero(mant, dec))) {
+ return d;
+ }
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ try {
+ return createBigDecimal(numeric);
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ //$FALL-THROUGH$
+ default :
+ throw new NumberFormatException(str + " is not a valid number.");
+
+ }
+ }
+ //User doesn't have a preference on the return type, so let's start
+ //small and go from there...
+ if (expPos > -1 && expPos < length - 1) {
+ exp = str.substring(expPos + 1);
+ } else {
+ exp = null;
+ }
+ if (dec == null && exp == null) { // no decimal point and no exponent
+ //Must be an Integer, Long, Biginteger
+ try {
+ return createInteger(str);
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ try {
+ return createLong(str);
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ return createBigInteger(str);
+ }
+
+ //Must be a Float, Double, BigDecimal
+ try {
+ final Float f = createFloat(str);
+ final Double d = createDouble(str);
+ if (!f.isInfinite()
+ && !(f.floatValue() == 0.0F && !isZero(mant, dec))
+ && f.toString().equals(d.toString())) {
+ return f;
+ }
+ if (!d.isInfinite() && !(d.doubleValue() == 0.0D && !isZero(mant, dec))) {
+ final BigDecimal b = createBigDecimal(str);
+ if (b.compareTo(BigDecimal.valueOf(d.doubleValue())) == 0) {
+ return d;
+ }
+ return b;
+ }
+ } catch (final NumberFormatException ignored) {
+ // ignore the bad number
+ }
+ return createBigDecimal(str);
+ }
+
+ /**
+ * Utility method for {@link #createNumber(java.lang.String)}.
+ *
+ * <p>Returns mantissa of the given number.</p>
+ *
+ * @param str the string representation of the number
+ * @param stopPos the position of the exponent or decimal point
+ * @return mantissa of the given number
+ */
+ private static String getMantissa(final String str, final int stopPos) {
+ final char firstChar = str.charAt(0);
+ final boolean hasSign = firstChar == '-' || firstChar == '+';
+
+ return hasSign ? str.substring(1, stopPos) : str.substring(0, stopPos);
+ }
+
+ /**
+ * Utility method for {@link #createNumber(java.lang.String)}.
+ *
+ * <p>This will check if the magnitude of the number is zero by checking if there
+ * are only zeros before and after the decimal place.</p>
+ *
+ * <p>Note: It is <strong>assumed</strong> 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"}.</p>
+ *
+ * <p>Thus the method only requires checking if both arguments are null, empty or
+ * contain only zeros.</p>
+ *
+ * <p>Given {@code s = mant + "." + dec}:</p>
+ * <ul>
+ * <li>{@code true} if s is {@code "0.0"}
+ * <li>{@code true} if s is {@code "0."}
+ * <li>{@code true} if s is {@code ".0"}
+ * <li>{@code false} otherwise (this assumes {@code "."} is not possible)
+ * </ul>
+ *
+ * @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)}.
+ *
+ * <p>Returns {@code true} if s is {@code null} or empty.</p>
+ *
+ * @param str the String to check
+ * @return if it is all zeros or {@code null}
+ */
+ private static boolean isAllZeros(final String str) {
+ if (str == null) {
+ return true;
+ }
+ for (int i = str.length() - 1; i >= 0; i--) {
+ if (str.charAt(i) != '0') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convert a {@link String} to a {@link Float}.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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}.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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
+ * hex (0xhhhh) and octal (0dddd) notations.
+ * N.B. a leading zero means octal; spaces are not trimmed.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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 hex (0Xhhhh) and octal (0ddd) notations.
+ * N.B. a leading zero means octal; spaces are not trimmed.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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 hex (0x or #) and octal (0) notations.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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}.
+ *
+ * <p>Returns {@code null} if the string is {@code null}.</p>
+ *
+ * @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.
+ *
+ * <p>If any value is {@code NaN}, {@code NaN} is
+ * returned. Infinity is handled.</p>
+ *
+ * @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.
+ *
+ * <p>If any value is {@code NaN}, {@code NaN} is
+ * returned. Infinity is handled.</p>
+ *
+ * @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.
+ *
+ * <p>If any value is {@code NaN}, {@code NaN} is
+ * returned. Infinity is handled.</p>
+ *
+ * @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.
+ *
+ * <p>If any value is {@code NaN}, {@code NaN} is
+ * returned. Infinity is handled.</p>
+ *
+ * @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.
+ *
+ * <p>{@code null} and empty String will return
+ * {@code false}.</p>
+ *
+ * @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 a valid Java number.
+ *
+ * <p>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).</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>{@code null} and empty/blank {@link String} will return
+ * {@code false}.</p>
+ *
+ * <p>Note, {@link #createNumber(String)} should return a number for every
+ * input resulting in {@code true}.</p>
+ *
+ * @param str the {@link String} to check
+ * @return {@code true} if the string is a correctly formatted number
+ * @since 3.3 the code supports hex {@code 0Xhhh} an
+ * octal {@code 0ddd} validation
+ * @deprecated This feature will be removed in Lang 4.0,
+ * use {@link NumberUtils#isCreatable(String)} instead
+ */
+ @Deprecated
+ public static boolean isNumber(final String str) {
+ return isCreatable(str);
+ }
+
+ /**
+ * Checks whether the String a valid Java number.
+ *
+ * <p>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).</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>{@code null} and empty/blank {@link String} will return
+ * {@code false}.</p>
+ *
+ * <p>Note, {@link #createNumber(String)} should return a number for every
+ * input resulting in {@code true}.</p>
+ *
+ * @param str the {@link String} to check
+ * @return {@code true} if the string is a correctly formatted number
+ * @since 3.5
+ */
+ public static boolean isCreatable(final String str) {
+ if (StringUtils.isEmpty(str)) {
+ return false;
+ }
+ final char[] chars = str.toCharArray();
+ int sz = chars.length;
+ boolean hasExp = false;
+ boolean hasDecPoint = false;
+ boolean allowSigns = false;
+ boolean foundDigit = false;
+ // deal with any possible sign up front
+ final int start = chars[0] == '-' || chars[0] == '+' ? 1 : 0;
+ if (sz > start + 1 && chars[start] == '0' && !StringUtils.contains(str, '.')) { // leading 0, skip if is a decimal number
+ if (chars[start + 1] == 'x' || chars[start + 1] == 'X') { // leading 0x/0X
+ int i = start + 2;
+ if (i == sz) {
+ return false; // str == "0x"
+ }
+ // checking hex (it can't be anything else)
+ for (; i < chars.length; i++) {
+ if ((chars[i] < '0' || chars[i] > '9')
+ && (chars[i] < 'a' || chars[i] > 'f')
+ && (chars[i] < 'A' || chars[i] > 'F')) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (Character.isDigit(chars[start + 1])) {
+ // leading 0, but not hex, must be octal
+ int i = start + 1;
+ for (; i < chars.length; i++) {
+ if (chars[i] < '0' || chars[i] > '7') {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ sz--; // don't want to loop to the last char, check it afterwords
+ // for type qualifiers
+ int i = start;
+ // loop to the next to last char or to the last char if we need another digit to
+ // make a valid number (e.g. chars[0..5] = "1234E")
+ while (i < sz || i < sz + 1 && allowSigns && !foundDigit) {
+ if (chars[i] >= '0' && chars[i] <= '9') {
+ foundDigit = true;
+ allowSigns = false;
+
+ } else if (chars[i] == '.') {
+ if (hasDecPoint || hasExp) {
+ // two decimal points or dec in exponent
+ return false;
+ }
+ hasDecPoint = true;
+ } else if (chars[i] == 'e' || chars[i] == 'E') {
+ // we've already taken care of hex.
+ if (hasExp) {
+ // two E's
+ return false;
+ }
+ if (!foundDigit) {
+ return false;
+ }
+ hasExp = true;
+ allowSigns = true;
+ } else if (chars[i] == '+' || chars[i] == '-') {
+ if (!allowSigns) {
+ return false;
+ }
+ allowSigns = false;
+ foundDigit = false; // we need a digit after the E
+ } else {
+ return false;
+ }
+ i++;
+ }
+ if (i < chars.length) {
+ if (chars[i] >= '0' && chars[i] <= '9') {
+ // no type qualifier, OK
+ return true;
+ }
+ if (chars[i] == 'e' || chars[i] == 'E') {
+ // can't have an E at the last byte
+ return false;
+ }
+ if (chars[i] == '.') {
+ if (hasDecPoint || hasExp) {
+ // two decimal points or dec in exponent
+ return false;
+ }
+ // single trailing decimal point after non-exponent is ok
+ return foundDigit;
+ }
+ if (!allowSigns
+ && (chars[i] == 'd'
+ || chars[i] == 'D'
+ || chars[i] == 'f'
+ || chars[i] == 'F')) {
+ return foundDigit;
+ }
+ if (chars[i] == 'l'
+ || chars[i] == 'L') {
+ // not allowing L with an exponent or decimal point
+ return foundDigit && !hasExp && !hasDecPoint;
+ }
+ // last character is illegal
+ return false;
+ }
+ // allowSigns is true iff the val ends in 'E'
+ // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass
+ return !allowSigns && foundDigit;
+ }
+
+ /**
+ * Checks whether the given String is a parsable number.
+ *
+ * <p>Parsable numbers include those Strings understood by {@link Integer#parseInt(String)},
+ * {@link Long#parseLong(String)}, {@link Float#parseFloat(String)} or
+ * {@link Double#parseDouble(String)}. This method can be used instead of catching {@link java.text.ParseException}
+ * when calling one of those methods.</p>
+ *
+ * <p>Hexadecimal and scientific notations are <strong>not</strong> considered parsable.
+ * See {@link #isCreatable(String)} on those cases.</p>
+ *
+ * <p>{@code null} and empty String will return {@code false}.</p>
+ *
+ * @param str the String to check.
+ * @return {@code true} if the string is a parsable number.
+ * @since 3.4
+ */
+ public static boolean isParsable(final String str) {
+ if (StringUtils.isEmpty(str)) {
+ return false;
+ }
+ if (str.charAt(str.length() - 1) == '.') {
+ return false;
+ }
+ if (str.charAt(0) == '-') {
+ if (str.length() == 1) {
+ return false;
+ }
+ return withDecimalsParsing(str, 1);
+ }
+ return withDecimalsParsing(str, 0);
+ }
+
+ private static boolean withDecimalsParsing(final String str, final int beginIdx) {
+ int decimalPoints = 0;
+ for (int i = beginIdx; i < str.length(); i++) {
+ final boolean isDecimalPoint = str.charAt(i) == '.';
+ if (isDecimalPoint) {
+ decimalPoints++;
+ }
+ if (decimalPoints > 1) {
+ return false;
+ }
+ if (!isDecimalPoint && !Character.isDigit(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares two {@code int} values numerically. This is the same functionality as provided in Java 7.
+ *
+ * @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 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;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/math/package-info.java b/src/main/java/org/apache/commons/lang3/math/package-info.java
new file mode 100644
index 000000000..b83e078ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/math/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Extends {@link java.math} for business mathematical classes.
+ * This package is intended for business mathematical use, not scientific use.
+ * See <a href="https://commons.apache.org/proper/commons-math/">Commons Math</a> for a more complete set of mathematical classes.
+ * These classes are immutable, and therefore thread-safe.
+ *
+ * <p>Although Commons Math also exists, some basic mathematical functions are contained within Lang.
+ * These include classes to a {@link org.apache.commons.lang3.math.Fraction} class, various utilities for random numbers, and the flagship class, {@link org.apache.commons.lang3.math.NumberUtils} which contains a handful of classic number functions.</p>
+ *
+ * <p>There are two aspects of this package that should be highlighted.
+ * The first is {@link org.apache.commons.lang3.math.NumberUtils#createNumber(String)}, a method which does its best to convert a String into a {@link java.lang.Number} object.
+ * You have no idea what type of Number it will return, so you should call the relevant {@code xxxValue} method when you reach the point of needing a number.
+ * NumberUtils also has a related {@link org.apache.commons.lang3.math.NumberUtils#isCreatable(String)} method.</p>
+ *
+ * @since 2.0
+ */
+package org.apache.commons.lang3.math;
diff --git a/src/main/java/org/apache/commons/lang3/mutable/Mutable.java b/src/main/java/org/apache/commons/lang3/mutable/Mutable.java
new file mode 100644
index 000000000..e6c748524
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/Mutable.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.mutable;
+
+/**
+ * Provides mutable access to a value.
+ * <p>
+ * {@link Mutable} is used as a generic interface to the implementations in this package.
+ * </p>
+ * <p>
+ * A typical use case would be to enable a primitive or string to be passed to a method and allow that method to
+ * effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in
+ * a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects.
+ * </p>
+ *
+ * @param <T> the type to set and get
+ * @since 2.1
+ */
+public interface Mutable<T> {
+
+ /**
+ * Gets the value of this mutable.
+ *
+ * @return the stored value
+ */
+ T getValue();
+
+ /**
+ * Sets the value of this mutable.
+ *
+ * @param value
+ * the value to store
+ * @throws NullPointerException
+ * if the object is null and null is invalid
+ * @throws ClassCastException
+ * if the type is invalid
+ */
+ void setValue(T value);
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java b/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java
new file mode 100644
index 000000000..68511c33b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableBoolean.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.mutable;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.BooleanUtils;
+
+/**
+ * A mutable {@code boolean} wrapper.
+ * <p>
+ * Note that as MutableBoolean does not extend Boolean, it is not treated by String.format as a Boolean parameter.
+ * </p>
+ *
+ * @see Boolean
+ * @since 2.2
+ */
+public class MutableBoolean implements Mutable<Boolean>, Serializable, Comparable<MutableBoolean> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -4830728138360036487L;
+
+ /** The mutable value. */
+ private boolean value;
+
+ /**
+ * Constructs a new MutableBoolean with the default value of false.
+ */
+ public MutableBoolean() {
+ }
+
+ /**
+ * Constructs a new MutableBoolean with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableBoolean(final boolean value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableBoolean with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableBoolean(final Boolean value) {
+ this.value = value.booleanValue();
+ }
+
+ /**
+ * Gets the value as a Boolean instance.
+ *
+ * @return the value as a Boolean, never null
+ */
+ @Override
+ public Boolean getValue() {
+ return Boolean.valueOf(this.value);
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the value to set
+ */
+ public void setValue(final boolean value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value to false.
+ *
+ * @since 3.3
+ */
+ public void setFalse() {
+ this.value = false;
+ }
+
+ /**
+ * Sets the value to true.
+ *
+ * @since 3.3
+ */
+ public void setTrue() {
+ this.value = true;
+ }
+
+ /**
+ * Sets the value from any Boolean instance.
+ *
+ * @param value the value to set, not null
+ * @throws NullPointerException if the object is null
+ */
+ @Override
+ public void setValue(final Boolean value) {
+ 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.
+ *
+ * @return a Boolean instance containing the value from this mutable, never null
+ * @since 2.5
+ */
+ public Boolean toBoolean() {
+ return Boolean.valueOf(booleanValue());
+ }
+
+ /**
+ * Compares this object to the specified object. The result is {@code true} 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.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java b/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java
new file mode 100644
index 000000000..94d973805
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableByte.java
@@ -0,0 +1,381 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * A mutable {@code byte} wrapper.
+ * <p>
+ * Note that as MutableByte does not extend Byte, it is not treated by String.format as a Byte parameter.
+ * </p>
+ *
+ * @see Byte
+ * @since 2.1
+ */
+public class MutableByte extends Number implements Comparable<MutableByte>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -1585823265L;
+
+ /** The mutable value. */
+ private byte value;
+
+ /**
+ * Constructs a new MutableByte with the default value of zero.
+ */
+ public MutableByte() {
+ }
+
+ /**
+ * Constructs a new MutableByte with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableByte(final byte value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableByte with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableByte(final Number value) {
+ this.value = value.byteValue();
+ }
+
+ /**
+ * Constructs a new MutableByte parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a byte
+ * @since 2.5
+ */
+ public MutableByte(final String value) {
+ 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.
+ *
+ * @param operand the value to add, not null
+ * @since 2.2
+ */
+ public void add(final byte operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(final Number operand) {
+ 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.
+ *
+ * @param operand the quantity to add, not null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public byte addAndGet(final byte operand) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public byte addAndGet(final Number operand) {
+ this.value += operand.byteValue();
+ 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 byte getAndAdd(final byte operand) {
+ final byte 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 byte getAndAdd(final Number operand) {
+ final byte last = value;
+ this.value += operand.byteValue();
+ return last;
+ }
+
+ // shortValue relies on Number implementation
+ /**
+ * Returns the value of this MutableByte as a byte.
+ *
+ * @return the numeric value represented by this object after conversion to type byte.
+ */
+ @Override
+ public byte byteValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableByte as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Byte.
+ *
+ * @return a Byte instance containing the value from this mutable
+ */
+ public Byte toByte() {
+ return Byte.valueOf(byteValue());
+ }
+
+ /**
+ * Compares this object to the specified object. The result is {@code true} 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.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java b/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java
new file mode 100644
index 000000000..3f0d0e6bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableDouble.java
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable {@code double} wrapper.
+ * <p>
+ * Note that as MutableDouble does not extend Double, it is not treated by String.format as a Double parameter.
+ * </p>
+ *
+ * @see Double
+ * @since 2.1
+ */
+public class MutableDouble extends Number implements Comparable<MutableDouble>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1587163916L;
+
+ /** The mutable value. */
+ private double value;
+
+ /**
+ * Constructs a new MutableDouble with the default value of zero.
+ */
+ public MutableDouble() {
+ }
+
+ /**
+ * Constructs a new MutableDouble with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableDouble(final double value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableDouble with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableDouble(final Number value) {
+ this.value = value.doubleValue();
+ }
+
+ /**
+ * Constructs a new MutableDouble parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a double
+ * @since 2.5
+ */
+ public MutableDouble(final String value) {
+ 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.
+ *
+ * @param operand the value to add
+ * @since 2.2
+ */
+ public void add(final double operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(final Number operand) {
+ 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.
+ *
+ * @param operand the quantity to add, not null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public double addAndGet(final double operand) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public double addAndGet(final Number operand) {
+ this.value += operand.doubleValue();
+ 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 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;
+ }
+
+ /**
+ * Returns the value of this MutableDouble as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Double.
+ *
+ * @return a Double instance containing the value from this mutable, never null
+ */
+ public Double toDouble() {
+ return Double.valueOf(doubleValue());
+ }
+
+ /**
+ * Compares this object against the specified object. The result is {@code true} if and only if the argument
+ * is not {@code null} and is a {@link Double} object that represents a double that has the identical
+ * bit pattern to the bit pattern of the double represented by this object. For this purpose, two
+ * {@code double} values are considered to be the same if and only if the method
+ * {@link Double#doubleToLongBits(double)}returns the same long value when applied to each.
+ * <p>
+ * Note that in most cases, for two instances of class {@link Double},{@code d1} and {@code d2},
+ * the value of {@code d1.equals(d2)} is {@code true} if and only if <blockquote>
+ *
+ * <pre>
+ * d1.doubleValue()&nbsp;== d2.doubleValue()
+ * </pre>
+ *
+ * </blockquote>
+ * <p>
+ * also has the value {@code true}. However, there are two exceptions:
+ * <ul>
+ * <li>If {@code d1} and {@code d2} both represent {@code Double.NaN}, then the
+ * {@code equals} method returns {@code true}, even though {@code Double.NaN==Double.NaN} has
+ * the value {@code false}.
+ * <li>If {@code d1} represents {@code +0.0} while {@code d2} represents {@code -0.0},
+ * or vice versa, the {@code equal} test has the value {@code false}, even though
+ * {@code +0.0==-0.0} has the value {@code true}. This allows hashtables to operate properly.
+ * </ul>
+ *
+ * @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) {
+ return obj instanceof MutableDouble
+ && Double.doubleToLongBits(((MutableDouble) obj).value) == Double.doubleToLongBits(value);
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ final long bits = Double.doubleToLongBits(value);
+ return (int) (bits ^ bits >>> 32);
+ }
+
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ @Override
+ public int compareTo(final MutableDouble other) {
+ return Double.compare(this.value, other.value);
+ }
+
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java b/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java
new file mode 100644
index 000000000..d1aa9f802
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableFloat.java
@@ -0,0 +1,408 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+/**
+ * A mutable {@code float} wrapper.
+ * <p>
+ * Note that as MutableFloat does not extend Float, it is not treated by String.format as a Float parameter.
+ * </p>
+ *
+ * @see Float
+ * @since 2.1
+ */
+public class MutableFloat extends Number implements Comparable<MutableFloat>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 5787169186L;
+
+ /** The mutable value. */
+ private float value;
+
+ /**
+ * Constructs a new MutableFloat with the default value of zero.
+ */
+ public MutableFloat() {
+ }
+
+ /**
+ * Constructs a new MutableFloat with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableFloat(final float value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableFloat with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableFloat(final Number value) {
+ this.value = value.floatValue();
+ }
+
+ /**
+ * Constructs a new MutableFloat parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a float
+ * @since 2.5
+ */
+ public MutableFloat(final String value) {
+ 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.
+ *
+ * @param operand the value to add, not null
+ * @since 2.2
+ */
+ public void add(final float operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(final Number operand) {
+ 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.
+ *
+ * @param operand the quantity to add, not null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public float addAndGet(final float operand) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public float addAndGet(final Number operand) {
+ this.value += operand.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;
+ }
+
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableFloat as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return (long) value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableFloat as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Float.
+ *
+ * @return a Float instance containing the value from this mutable, never null
+ */
+ public Float toFloat() {
+ return Float.valueOf(floatValue());
+ }
+
+ /**
+ * Compares this object against some other object. The result is {@code true} if and only if the argument is
+ * not {@code null} and is a {@link Float} object that represents a {@code float} that has the
+ * identical bit pattern to the bit pattern of the {@code float} represented by this object. For this
+ * purpose, two float values are considered to be the same if and only if the method
+ * {@link Float#floatToIntBits(float)}returns the same int value when applied to each.
+ * <p>
+ * Note that in most cases, for two instances of class {@link Float},{@code f1} and {@code f2},
+ * the value of {@code f1.equals(f2)} is {@code true} if and only if <blockquote>
+ *
+ * <pre>
+ * f1.floatValue() == f2.floatValue()
+ * </pre>
+ *
+ * </blockquote>
+ * <p>
+ * also has the value {@code true}. However, there are two exceptions:
+ * <ul>
+ * <li>If {@code f1} and {@code f2} both represent {@code Float.NaN}, then the
+ * {@code equals} method returns {@code true}, even though {@code Float.NaN==Float.NaN} has
+ * the value {@code false}.
+ * <li>If {@code f1} represents {@code +0.0f} while {@code f2} represents {@code -0.0f},
+ * or vice versa, the {@code equal} test has the value {@code false}, even though
+ * {@code 0.0f==-0.0f} has the value {@code true}.
+ * </ul>
+ * This definition allows hashtables to operate properly.
+ *
+ * @param obj the object to compare with, null returns false
+ * @return {@code true} if the objects are the same; {@code false} otherwise.
+ * @see Float#floatToIntBits(float)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof MutableFloat
+ && Float.floatToIntBits(((MutableFloat) obj).value) == Float.floatToIntBits(value);
+ }
+
+ /**
+ * Returns a suitable hash code for this mutable.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(value);
+ }
+
+ /**
+ * Compares this mutable to another in ascending order.
+ *
+ * @param other the other mutable to compare to, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ @Override
+ public int compareTo(final MutableFloat other) {
+ return Float.compare(this.value, other.value);
+ }
+
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java b/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java
new file mode 100644
index 000000000..99e78b972
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableInt.java
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * A mutable {@code int} wrapper.
+ * <p>
+ * Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter.
+ * </p>
+ *
+ * @see Integer
+ * @since 2.1
+ */
+public class MutableInt extends Number implements Comparable<MutableInt>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 512176391864L;
+
+ /** The mutable value. */
+ private int value;
+
+ /**
+ * Constructs a new MutableInt with the default value of zero.
+ */
+ public MutableInt() {
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableInt(final int value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableInt with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableInt(final Number value) {
+ this.value = value.intValue();
+ }
+
+ /**
+ * Constructs a new MutableInt parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into an int
+ * @since 2.5
+ */
+ public MutableInt(final String value) {
+ 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.
+ *
+ * @param operand the value to add, not null
+ * @since 2.2
+ */
+ public void add(final int operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(final Number operand) {
+ 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.
+ *
+ * @param operand the quantity to add, not null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public int addAndGet(final int operand) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public int addAndGet(final Number operand) {
+ this.value += operand.intValue();
+ 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 int getAndAdd(final int operand) {
+ final int 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 int getAndAdd(final Number operand) {
+ final int last = value;
+ this.value += operand.intValue();
+ return last;
+ }
+
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableInt as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableInt as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Integer.
+ *
+ * @return an Integer instance containing the value from this mutable, never null
+ */
+ public Integer toInteger() {
+ return Integer.valueOf(intValue());
+ }
+
+ /**
+ * Compares this object to the specified object. The result is {@code true} 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.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java b/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java
new file mode 100644
index 000000000..651c6994f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableLong.java
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * A mutable {@code long} wrapper.
+ * <p>
+ * Note that as MutableLong does not extend Long, it is not treated by String.format as a Long parameter.
+ * </p>
+ *
+ * @see Long
+ * @since 2.1
+ */
+public class MutableLong extends Number implements Comparable<MutableLong>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 62986528375L;
+
+ /** The mutable value. */
+ private long value;
+
+ /**
+ * Constructs a new MutableLong with the default value of zero.
+ */
+ public MutableLong() {
+ }
+
+ /**
+ * Constructs a new MutableLong with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableLong(final long value) {
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new MutableLong with the specified value.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableLong(final Number value) {
+ this.value = value.longValue();
+ }
+
+ /**
+ * Constructs a new MutableLong parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a long
+ * @since 2.5
+ */
+ public MutableLong(final String value) {
+ 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.
+ *
+ * @param operand the value to add, not null
+ * @since 2.2
+ */
+ public void add(final long operand) {
+ this.value += operand;
+ }
+
+ /**
+ * Adds a value to the value of this instance.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(final Number operand) {
+ 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.
+ *
+ * @param operand the quantity to add, not null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public long addAndGet(final long operand) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public long addAndGet(final Number operand) {
+ this.value += operand.longValue();
+ 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 long getAndAdd(final long operand) {
+ final long 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 long getAndAdd(final Number operand) {
+ final long last = value;
+ this.value += operand.longValue();
+ return last;
+ }
+
+ // shortValue and byteValue rely on Number implementation
+ /**
+ * Returns the value of this MutableLong as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableLong as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Long.
+ *
+ * @return a Long instance containing the value from this mutable, never null
+ */
+ public Long toLong() {
+ return Long.valueOf(longValue());
+ }
+
+ /**
+ * Compares this object to the specified object. The result is {@code true} 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.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java b/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java
new file mode 100644
index 000000000..7c371f783
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableObject.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.mutable;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * A mutable {@link Object} wrapper.
+ *
+ * @param <T> the type to set and get
+ * @since 2.1
+ */
+public class MutableObject<T> implements Mutable<T>, Serializable {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 86241875189L;
+
+ /** The mutable value. */
+ private T value;
+
+ /**
+ * Constructs a new MutableObject with the default value of {@code null}.
+ */
+ public MutableObject() {
+ }
+
+ /**
+ * Constructs a new MutableObject with the specified value.
+ *
+ * @param value the initial value to store
+ */
+ public MutableObject(final T value) {
+ 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}
+ * value as this object.
+ *
+ * @param obj the object to compare with, {@code null} returns {@code false}
+ * @return {@code true} if the objects are the same;
+ * {@code true} if the objects have equivalent {@code value} fields;
+ * {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() == obj.getClass()) {
+ final MutableObject<?> that = (MutableObject<?>) obj;
+ return this.value.equals(that.value);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the value's hash code or {@code 0} if the value is {@code null}.
+ *
+ * @return the value's hash code or {@code 0} if the value is {@code null}.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(value);
+ }
+
+ /**
+ * Returns the String value of this mutable.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return Objects.toString(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java b/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java
new file mode 100644
index 000000000..4378ad190
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/MutableShort.java
@@ -0,0 +1,381 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.mutable;
+
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * A mutable {@code short} wrapper.
+ * <p>
+ * Note that as MutableShort does not extend Short, it is not treated by String.format as a Short parameter.
+ * </p>
+ *
+ * @see Short
+ * @since 2.1
+ */
+public class MutableShort extends Number implements Comparable<MutableShort>, Mutable<Number> {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -2135791679L;
+
+ /** The mutable value. */
+ private short value;
+
+ /**
+ * Constructs a new MutableShort with the default value of zero.
+ */
+ public MutableShort() {
+ }
+
+ /**
+ * 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.
+ *
+ * @param value the initial value to store, not null
+ * @throws NullPointerException if the object is null
+ */
+ public MutableShort(final Number value) {
+ this.value = value.shortValue();
+ }
+
+ /**
+ * Constructs a new MutableShort parsing the given string.
+ *
+ * @param value the string to parse, not null
+ * @throws NumberFormatException if the string cannot be parsed into a short
+ * @since 2.5
+ */
+ public MutableShort(final String value) {
+ 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.
+ *
+ * @param operand the value to add, not null
+ * @throws NullPointerException if the object is null
+ * @since 2.2
+ */
+ public void add(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;
+ }
+
+ /**
+ * 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) {
+ this.value += operand;
+ return value;
+ }
+
+ /**
+ * 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
+ * @throws NullPointerException if {@code operand} is null
+ * @return the value associated with this instance after adding the operand
+ * @since 3.5
+ */
+ public short addAndGet(final Number operand) {
+ this.value += operand.shortValue();
+ 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 short getAndAdd(final short operand) {
+ final short 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 short getAndAdd(final Number operand) {
+ final short last = value;
+ this.value += operand.shortValue();
+ return last;
+ }
+
+ // byteValue relies on Number implementation
+ /**
+ * Returns the value of this MutableShort as a short.
+ *
+ * @return the numeric value represented by this object after conversion to type short.
+ */
+ @Override
+ public short shortValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as an int.
+ *
+ * @return the numeric value represented by this object after conversion to type int.
+ */
+ @Override
+ public int intValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a long.
+ *
+ * @return the numeric value represented by this object after conversion to type long.
+ */
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a float.
+ *
+ * @return the numeric value represented by this object after conversion to type float.
+ */
+ @Override
+ public float floatValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of this MutableShort as a double.
+ *
+ * @return the numeric value represented by this object after conversion to type double.
+ */
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ /**
+ * Gets this mutable as an instance of Short.
+ *
+ * @return a Short instance containing the value from this mutable, never null
+ */
+ public Short toShort() {
+ return Short.valueOf(shortValue());
+ }
+
+ /**
+ * Compares this object to the specified object. The result is {@code true} 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.
+ *
+ * @return the mutable value as a string
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/mutable/package-info.java b/src/main/java/org/apache/commons/lang3/mutable/package-info.java
new file mode 100644
index 000000000..1b5aaec70
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/mutable/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides typed mutable wrappers to primitive values and Object.
+ * These wrappers are similar to the wrappers provided by the Java API, but allow the wrapped value to be changed without needing to create a separate wrapper object.
+ * These classes are not thread-safe.
+ *
+ * @since 2.1
+ */
+package org.apache.commons.lang3.mutable;
diff --git a/src/main/java/org/apache/commons/lang3/package-info.java b/src/main/java/org/apache/commons/lang3/package-info.java
new file mode 100644
index 000000000..aca4f141c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/package-info.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides highly reusable static utility methods, chiefly concerned with adding value to the {@link java.lang} classes.
+ * Most of these classes are immutable and thus thread-safe.
+ * However {@link org.apache.commons.lang3.CharSet} is not currently guaranteed thread-safe under all circumstances.
+ *
+ * <p>The top level package contains various Utils classes, whilst there are various subpackages including {@link org.apache.commons.lang3.math}, {@link org.apache.commons.lang3.concurrent} and {@link org.apache.commons.lang3.builder}.
+ * Using the Utils classes is generally simplicity itself.
+ * They are the equivalent of global functions in another language, a collection of stand-alone, thread-safe, static methods.
+ * In contrast, subpackages may contain interfaces which may have to be implemented or classes which may need to be extended to get the full functionality from the code.
+ * They may, however, contain more global-like functions.</p>
+ *
+ * <p>Lang 3.0 requires JDK 1.5+, since Lang 3.2 it requires JDK 6+; The legacy release 2.6 requires JDK 1.2+.
+ * In both cases you can find features of later JDKs being maintained by us and likely to be removed or modified in favour of the JDK in the next major version.
+ * Note that Lang 3.0 uses a different package than its predecessors, allowing it to be used at the same time as an earlier version.</p>
+ *
+ * <p>You will find deprecated methods as you stroll through the Lang documentation. These are removed in the next major version.</p>
+ *
+ * <p>All util classes contain empty public constructors with warnings not to use.
+ * This may seem an odd thing to do, but it allows tools like Velocity to access the class as if it were a bean.
+ * In other words, yes we know about private constructors and have chosen not to use them.</p>
+ *
+ * <h2>String manipulation - StringUtils, StringEscapeUtils, RandomStringUtils</h2>
+ *
+ * <p>Lang has a series of String utilities.
+ * The first is {@link org.apache.commons.lang3.StringUtils}, oodles and oodles of functions which tweak, transform, squeeze and cuddle {@link java.lang.String java.lang.Strings}.
+ * In addition to StringUtils, there are a series of other String manipulating classes; {@link org.apache.commons.lang3.RandomStringUtils} and {@link org.apache.commons.lang3.StringEscapeUtils StringEscapeUtils}.
+ * RandomStringUtils speaks for itself.
+ * It's provides ways in which to generate pieces of text, such as might be used for default passwords.
+ * StringEscapeUtils contains methods to escape and unescape Java, JavaScript, JSON, HTML and XML.</p>
+ *
+ * <p>These are ideal classes to start using if you're looking to get into Lang.
+ * StringUtils' {@link org.apache.commons.lang3.StringUtils#capitalize(String)}, {@link org.apache.commons.lang3.StringUtils#substringBetween(String, String)}/{@link org.apache.commons.lang3.StringUtils#substringBefore(String, String) Before}/{@link org.apache.commons.lang3.StringUtils#substringAfter(String, String) After}, {@link org.apache.commons.lang3.StringUtils#split(String)} and {@link org.apache.commons.lang3.StringUtils#join(Object[])} are good methods to begin with.</p>
+ *
+ * <h2>Character handling - CharSetUtils, CharSet, CharRange, CharUtils</h2>
+ *
+ * <p>In addition to dealing with Strings, it's also important to deal with chars and Characters.
+ * {@link org.apache.commons.lang3.CharUtils} exists for this purpose, while {@link org.apache.commons.lang3.CharSetUtils} exists for set-manipulation of Strings.
+ * Be careful, although CharSetUtils takes an argument of type String, it is only as a set of characters.
+ * For example, {@code CharSetUtils.delete("testtest", "tr")} will remove all t's and all r's from the String, not just the String "tr".</p>
+ *
+ * <p>{@link org.apache.commons.lang3.CharRange} and {@link org.apache.commons.lang3.CharSet} are both used internally by CharSetUtils, and will probably rarely be used.</p>
+ *
+ * <h2>JVM interaction - SystemUtils, CharEncoding</h2>
+ *
+ * <p>SystemUtils is a simple little class which makes it easy to find out information about which platform you are on.
+ * For some, this is a necessary evil. It was never something I expected to use myself until I was trying to ensure that Commons Lang itself compiled under JDK 1.2.
+ * Having pushed out a few JDK 1.3 bits that had slipped in ({@code Collections.EMPTY_MAP} is a classic offender), I then found that one of the Unit Tests was dying mysteriously under JDK 1.2, but ran fine under JDK 1.3.
+ * There was no obvious solution and I needed to move onwards, so the simple solution was to wrap that particular test in a <code>if (SystemUtils.isJavaVersionAtLeast(1.3f)) {</code>, make a note and move on.</p>
+ *
+ * <p>The {@link org.apache.commons.lang3.CharEncoding} class is also used to interact with the Java environment and may be used to see which character encodings are supported in a particular environment.</p>
+ *
+ * <h2>Serialization - SerializationUtils, SerializationException</h2>
+ *
+ * <p>Serialization doesn't have to be that hard!
+ * A simple util class can take away the pain, plus it provides a method to clone an object by unserializing and reserializing, an old Java trick.</p>
+ *
+ * <h3>Assorted functions - ObjectUtils, ClassUtils, ArrayUtils, BooleanUtils</h3>
+ *
+ * <p>Would you believe it, {@link org.apache.commons.lang3.ObjectUtils} contains handy functions for Objects, mainly null-safe implementations of the methods on {@link java.lang.Object}.</p>
+ *
+ * <p>{@link org.apache.commons.lang3.ClassUtils} is largely a set of helper methods for reflection.
+ * Of special note are the comparators hidden away in ClassUtils, useful for sorting Class and Package objects by name; however they merely sort alphabetically and don't understand the common habit of sorting {@code java} and {@code javax} first.</p>
+ *
+ * <p>Next up, {@link org.apache.commons.lang3.ArrayUtils}.
+ * This is a big one with many methods and many overloads of these methods so it is probably worth an in depth look here.
+ * Before we begin, assume that every method mentioned is overloaded for all the primitives and for Object.
+ * Also, the short-hand 'xxx' implies a generic primitive type, but usually also includes Object.</p>
+ *
+ * <ul>
+ * <li>ArrayUtils provides singleton empty arrays for all the basic types. These will largely be of use in the Collections API with its toArray methods, but also will be of use with methods which want to return an empty array on error.</li>
+ * <li>{@code add(xxx[], xxx)} will add a primitive type to an array, resizing the array as you'd expect. Object is also supported. </li>
+ * <li>{@code clone(xxx[])} clones a primitive or Object array. </li>
+ * <li>{@code contains(xxx[], xxx)} searches for a primitive or Object in a primitive or Object array. </li>
+ * <li>{@code getLength(Object)} returns the length of any array or an IllegalArgumentException if the parameter is not an array. {@code hashCode(Object)}, {@code equals(Object, Object)}, {@code toString(Object)} </li>
+ * <li>{@code indexOf(xxx[], xxx)} and {@code indexOf(xxx[], xxx, int)} are copies of the classic String methods, but this time for primitive/Object arrays. In addition, a lastIndexOf set of methods exists. </li>
+ * <li>{@code isEmpty(xxx[])} lets you know if an array is zero-sized or null. </li>
+ * <li>{@code isSameLength(xxx[], xxx[])} returns true if the arrays are the same length. </li>
+ * <li>Along side the add methods, there are also remove methods of two types. The first type remove the value at an index, {@code remove(xxx[], int)}, while the second type remove the first value from the array, {@code remove(xxx[], xxx)}. </li>
+ * <li>Nearing the end now. The {@code reverse(xxx[])} method turns an array around. </li>
+ * <li>The {@code subarray(xxx[], int, int)} method splices an array out of a larger array. </li>
+ * <li>Primitive to primitive wrapper conversion is handled by the {@code toObject(xxx[])} and {@code toPrimitive(Xxx[])} methods. </li>
+ * </ul>
+ *
+ * <p>Lastly, {@link org.apache.commons.lang3.ArrayUtils#toMap(Object[])} is worthy of special note.
+ * It is not a heavily overloaded method for working with arrays, but a simple way to create Maps from literals.</p>
+ *
+ * <h4>Using toMap</h4>
+ * <pre>
+ * <code>
+ * Map colorMap = ArrayUtils.toMap(new String[][] {{
+ * {"RED", "#FF0000"},
+ * {"GREEN", "#00FF00"},
+ * {"BLUE", "#0000FF"}
+ * });
+ * </code>
+ * </pre>
+ *
+ * <p>Our final util class is {@link org.apache.commons.lang3.BooleanUtils}.
+ * It contains various Boolean acting methods, probably of most interest is the {@link org.apache.commons.lang3.BooleanUtils#toBoolean(String)} method which turns various positive/negative Strings into a Boolean object, and not just true/false as with Boolean.valueOf.</p>
+ *
+ * <h2>Flotsam - BitField, Validate</h2>
+ * <p>On reaching the end of our package, we are left with a couple of classes that haven't fit any of the topics so far.</p>
+ * <p>The {@link org.apache.commons.lang3.BitField} class provides a wrapper class around the classic bitmask integer, whilst the {@link org.apache.commons.lang3.Validate} class may be used for assertions (remember, we support Java 1.2).</p>
+ *
+ * @since 1.0
+ */
+package org.apache.commons.lang3;
diff --git a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
new file mode 100644
index 000000000..fda2fa7f7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Utility reflection methods focused on constructors, modeled after
+ * {@link MethodUtils}.
+ *
+ * <h2>Known Limitations</h2>
+ * <h3>Accessing Public Constructors In A Default Access Superclass</h3>
+ * <p>There is an issue when invoking {@code public} constructors
+ * contained in a default access superclass. Reflection correctly locates these
+ * constructors and assigns them as {@code public}. However, an
+ * {@link IllegalAccessException} is thrown if the constructor is
+ * invoked.</p>
+ *
+ * <p>{@link ConstructorUtils} contains a workaround for this situation: it
+ * will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this constructor. If this
+ * call succeeds, then the method can be invoked as normal. This call will only
+ * succeed when the application has sufficient security privileges. If this call
+ * fails then a warning will be logged and the method may fail.</p>
+ *
+ * @since 2.5
+ */
+public class ConstructorUtils {
+
+ /**
+ * ConstructorUtils instances should NOT be constructed in standard
+ * programming. Instead, the class should be used as
+ * {@code ConstructorUtils.invokeConstructor(cls, args)}.
+ *
+ * <p>This constructor is {@code public} to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public ConstructorUtils() {
+ }
+
+ /**
+ * Returns a new instance of the specified class inferring the right constructor
+ * from the types of the arguments.
+ *
+ * <p>This locates and calls a constructor.
+ * The constructor signature must match the argument types by assignment compatibility.</p>
+ *
+ * @param <T> the type to be constructed
+ * @param cls the class to be constructed, not {@code null}
+ * @param args the array of arguments, {@code null} treated as empty
+ * @return new instance of {@code cls}, not {@code null}
+ *
+ * @throws NullPointerException if {@code cls} is {@code null}
+ * @throws NoSuchMethodException if a matching constructor cannot be found
+ * @throws IllegalAccessException if invocation is not permitted by security
+ * @throws InvocationTargetException if an error occurs on invocation
+ * @throws InstantiationException if an error occurs on instantiation
+ * @see #invokeConstructor(Class, Object[], Class[])
+ */
+ public static <T> T invokeConstructor(final Class<T> cls, Object... args)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+ InstantiationException {
+ args = ArrayUtils.nullToEmpty(args);
+ return invokeConstructor(cls, args, ClassUtils.toClass(args));
+ }
+
+ /**
+ * Returns a new instance of the specified class choosing the right constructor
+ * from the list of parameter types.
+ *
+ * <p>This locates and calls a constructor.
+ * The constructor signature must match the parameter types by assignment compatibility.</p>
+ *
+ * @param <T> the type to be constructed
+ * @param cls the class to be constructed, not {@code null}
+ * @param args the array of arguments, {@code null} treated as empty
+ * @param parameterTypes the array of parameter types, {@code null} treated as empty
+ * @return new instance of {@code cls}, not {@code null}
+ *
+ * @throws NullPointerException if {@code cls} is {@code null}
+ * @throws NoSuchMethodException if a matching constructor cannot be found
+ * @throws IllegalAccessException if invocation is not permitted by security
+ * @throws InvocationTargetException if an error occurs on invocation
+ * @throws InstantiationException if an error occurs on instantiation
+ * @see Constructor#newInstance
+ */
+ public static <T> T invokeConstructor(final Class<T> cls, Object[] args, Class<?>[] parameterTypes)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+ InstantiationException {
+ args = ArrayUtils.nullToEmpty(args);
+ parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
+ final Constructor<T> ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
+ if (ctor == null) {
+ throw new NoSuchMethodException(
+ "No such accessible constructor on object: " + cls.getName());
+ }
+ if (ctor.isVarArgs()) {
+ final Class<?>[] methodParameterTypes = ctor.getParameterTypes();
+ args = MethodUtils.getVarArgs(args, methodParameterTypes);
+ }
+ return ctor.newInstance(args);
+ }
+
+ /**
+ * Returns a new instance of the specified class inferring the right constructor
+ * from the types of the arguments.
+ *
+ * <p>This locates and calls a constructor.
+ * The constructor signature must match the argument types exactly.</p>
+ *
+ * @param <T> the type to be constructed
+ * @param cls the class to be constructed, not {@code null}
+ * @param args the array of arguments, {@code null} treated as empty
+ * @return new instance of {@code cls}, not {@code null}
+ *
+ * @throws NullPointerException if {@code cls} is {@code null}
+ * @throws NoSuchMethodException if a matching constructor cannot be found
+ * @throws IllegalAccessException if invocation is not permitted by security
+ * @throws InvocationTargetException if an error occurs on invocation
+ * @throws InstantiationException if an error occurs on instantiation
+ * @see #invokeExactConstructor(Class, Object[], Class[])
+ */
+ public static <T> T invokeExactConstructor(final Class<T> cls, Object... args)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+ InstantiationException {
+ args = ArrayUtils.nullToEmpty(args);
+ return invokeExactConstructor(cls, args, ClassUtils.toClass(args));
+ }
+
+ /**
+ * Returns a new instance of the specified class choosing the right constructor
+ * from the list of parameter types.
+ *
+ * <p>This locates and calls a constructor.
+ * The constructor signature must match the parameter types exactly.</p>
+ *
+ * @param <T> the type to be constructed
+ * @param cls the class to be constructed, not {@code null}
+ * @param args the array of arguments, {@code null} treated as empty
+ * @param parameterTypes the array of parameter types, {@code null} treated as empty
+ * @return new instance of {@code cls}, not {@code null}
+ *
+ * @throws NullPointerException if {@code cls} is {@code null}
+ * @throws NoSuchMethodException if a matching constructor cannot be found
+ * @throws IllegalAccessException if invocation is not permitted by security
+ * @throws InvocationTargetException if an error occurs on invocation
+ * @throws InstantiationException if an error occurs on instantiation
+ * @see Constructor#newInstance
+ */
+ public static <T> T invokeExactConstructor(final Class<T> cls, Object[] args,
+ Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException, InstantiationException {
+ args = ArrayUtils.nullToEmpty(args);
+ parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
+ final Constructor<T> ctor = getAccessibleConstructor(cls, parameterTypes);
+ if (ctor == null) {
+ throw new NoSuchMethodException(
+ "No such accessible constructor on object: "+ cls.getName());
+ }
+ return ctor.newInstance(args);
+ }
+
+ /**
+ * Finds a constructor given a class and signature, checking accessibility.
+ *
+ * <p>This finds the constructor and ensures that it is accessible.
+ * The constructor signature must match the parameter types exactly.</p>
+ *
+ * @param <T> 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 <T> Constructor<T> getAccessibleConstructor(final Class<T> 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.
+ *
+ * <p>This simply ensures that the constructor is accessible.</p>
+ *
+ * @param <T> 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 <T> Constructor<T> getAccessibleConstructor(final Constructor<T> ctor) {
+ Objects.requireNonNull(ctor, "ctor");
+ return MemberUtils.isAccessible(ctor)
+ && isAccessible(ctor.getDeclaringClass()) ? ctor : null;
+ }
+
+ /**
+ * Finds an accessible constructor with compatible parameters.
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @param <T> 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 <T> Constructor<T> getMatchingAccessibleConstructor(final Class<T> 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<T> result = null;
+ /*
+ * (1) Class.getConstructors() is documented to return Constructor<T> 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<T> constructor = (Constructor<T>) ctor;
+ result = constructor;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Tests whether the specified class is generally accessible, i.e. is
+ * declared in an entirely {@code public} manner.
+ * @param type to check
+ * @return {@code true} if {@code type} and any enclosing classes are
+ * {@code public}.
+ */
+ private static boolean isAccessible(final Class<?> type) {
+ Class<?> cls = type;
+ while (cls != null) {
+ if (!ClassUtils.isPublic(cls)) {
+ return false;
+ }
+ cls = cls.getEnclosingClass();
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java b/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java
new file mode 100644
index 000000000..a2566512a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java
@@ -0,0 +1,837 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Utilities for working with {@link Field}s by reflection. Adapted and refactored from the dormant [reflect] Commons
+ * sandbox component.
+ * <p>
+ * The ability is provided to break the scoping restrictions coded by the programmer. This can allow fields to be
+ * changed that shouldn't be. This facility should be used with care.
+ * </p>
+ * @since 2.5
+ */
+public class FieldUtils {
+
+ /**
+ * {@link FieldUtils} instances should NOT be constructed in standard programming.
+ * <p>
+ * This constructor is {@code public} to permit tools that require a JavaBean instance to operate.
+ * </p>
+ */
+ public FieldUtils() {
+ }
+
+ /**
+ * Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces 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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty
+ */
+ public static Field getField(final Class<?> cls, final String fieldName) {
+ return MemberUtils.setAccessibleWorkaround(getField(cls, fieldName, false));
+ }
+
+ /**
+ * Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces 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 blank or empty or is matched at multiple places
+ * in the inheritance hierarchy
+ */
+ public static Field getField(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");
+ // FIXME is this workaround still needed? lang requires Java 6
+ // Sun Java 1.3 has a bugged implementation of getField hence we write the
+ // code ourselves
+
+ // getField() will return the Field object with the declaring class
+ // set correctly to the class that declares the field. Thus requesting the
+ // field on a subclass will return the field from the superclass.
+ //
+ // priority order for lookup:
+ // searchclass private/protected/package/public
+ // superclass protected/package/public
+ // private/different package blocks access to further superclasses
+ // implementedinterface public
+
+ // check up the superclass hierarchy
+ for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
+ try {
+ final Field field = acls.getDeclaredField(fieldName);
+ // getDeclaredField checks for non-public scopes as well
+ // and it returns accurate results
+ if (!MemberUtils.isPublic(field)) {
+ if (!forceAccess) {
+ continue;
+ }
+ field.setAccessible(true);
+ }
+ return field;
+ } catch (final NoSuchFieldException ignored) {
+ // ignore
+ }
+ }
+ // check the public interface case. This must be manually searched for
+ // incase there is a public supersuperclass field hidden by a private/package
+ // superclass field.
+ Field match = null;
+ for (final Class<?> class1 : ClassUtils.getAllInterfaces(cls)) {
+ try {
+ final Field test = class1.getField(fieldName);
+ Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s"
+ + "; a matching field exists on two or more implemented interfaces.", fieldName, cls);
+ match = test;
+ } catch (final NoSuchFieldException ignored) {
+ // ignore
+ }
+ }
+ return match;
+ }
+
+ /**
+ * 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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is 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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is 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 IllegalArgumentException
+ * 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).
+ *
+ * @param cls
+ * the {@link Class} to query
+ * @return a list of Fields (possibly empty).
+ * @throws IllegalArgumentException
+ * if the class is {@code null}
+ * @since 3.2
+ */
+ public static List<Field> getAllFieldsList(final Class<?> cls) {
+ Objects.requireNonNull(cls, "cls");
+ final List<Field> allFields = new ArrayList<>();
+ Class<?> currentClass = cls;
+ while (currentClass != null) {
+ final Field[] declaredFields = currentClass.getDeclaredFields();
+ Collections.addAll(allFields, declaredFields);
+ currentClass = currentClass.getSuperclass();
+ }
+ return allFields;
+ }
+
+ /**
+ * 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 an array of Fields (possibly empty).
+ * @throws IllegalArgumentException
+ * if the class or annotation are {@code null}
+ * @since 3.4
+ */
+ public static Field[] getFieldsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
+ return getFieldsListWithAnnotation(cls, annotationCls).toArray(ArrayUtils.EMPTY_FIELD_ARRAY);
+ }
+
+ /**
+ * 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 IllegalArgumentException
+ * if the class or annotation are {@code null}
+ * @since 3.4
+ */
+ public static List<Field> getFieldsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> 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}.
+ *
+ * @param field
+ * to read
+ * @return the field value
+ * @throws IllegalArgumentException
+ * if the field is {@code null}, or 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 IllegalArgumentException
+ * if the field is {@code null} or 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 fieldName
+ * the field name to obtain
+ * @return the value of the field
+ * @throws IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
+ * not be found
+ * @throws IllegalAccessException
+ * if the field is not accessible
+ */
+ public static Object readStaticField(final Class<?> cls, final String fieldName) throws IllegalAccessException {
+ return readStaticField(cls, fieldName, false);
+ }
+
+ /**
+ * Reads the named {@code static} {@link Field}. Superclasses 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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, 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);
+ // already forced access above, don't repeat it here:
+ return readStaticField(field, false);
+ }
+
+ /**
+ * Gets the value of a {@code static} {@link Field} by name. The field must be {@code public}. 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 value of the field
+ * @throws IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
+ * not be found
+ * @throws IllegalAccessException
+ * if the field is not accessible
+ */
+ public static Object readDeclaredStaticField(final Class<?> cls, final String fieldName) throws IllegalAccessException {
+ return readDeclaredStaticField(cls, fieldName, false);
+ }
+
+ /**
+ * Gets the value of a {@code static} {@link Field} by name. 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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
+ * not be found
+ * @throws IllegalAccessException
+ * if the field is not made accessible
+ */
+ public static Object readDeclaredStaticField(final Class<?> cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
+ final Field field = getDeclaredField(cls, fieldName, forceAccess);
+ Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
+ // already forced access above, don't repeat it here:
+ return readStaticField(field, false);
+ }
+
+ /**
+ * Reads an accessible {@link Field}.
+ *
+ * @param field
+ * the field to use
+ * @param target
+ * the object to call on, may be {@code null} for {@code static} fields
+ * @return the field value
+ * @throws IllegalArgumentException
+ * if the field is {@code null}
+ * @throws IllegalAccessException
+ * if the field is not accessible
+ */
+ public static Object readField(final Field field, final Object target) throws IllegalAccessException {
+ return readField(field, target, false);
+ }
+
+ /**
+ * Reads a {@link Field}.
+ *
+ * @param field
+ * the field to use
+ * @param target
+ * the object to call on, may be {@code null} for {@code static} fields
+ * @param forceAccess
+ * whether to break scope restrictions using the
+ * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method.
+ * @return the field value
+ * @throws IllegalArgumentException
+ * if the field is {@code null}
+ * @throws IllegalAccessException
+ * if the field is not made accessible
+ */
+ public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException {
+ Objects.requireNonNull(field, "field");
+ if (forceAccess && !field.isAccessible()) {
+ field.setAccessible(true);
+ } else {
+ MemberUtils.setAccessibleWorkaround(field);
+ }
+ return field.get(target);
+ }
+
+ /**
+ * Reads the named {@code public} {@link Field}. Superclasses will be considered.
+ *
+ * @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 IllegalArgumentException
+ * if the class is {@code null}, or the field name is blank or empty or could not be found
+ * @throws IllegalAccessException
+ * if the named field is not {@code public}
+ */
+ public static Object readField(final Object target, final String fieldName) throws IllegalAccessException {
+ return readField(target, fieldName, false);
+ }
+
+ /**
+ * Reads the named {@link Field}. Superclasses will be considered.
+ *
+ * @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.
+ * @return the field value
+ * @throws IllegalArgumentException
+ * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
+ * @throws IllegalAccessException
+ * if the named field is not made accessible
+ */
+ public static Object readField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
+ Objects.requireNonNull(target, "target");
+ final Class<?> cls = target.getClass();
+ final Field field = getField(cls, fieldName, forceAccess);
+ Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls);
+ // already forced access above, don't repeat it here:
+ return readField(field, target, false);
+ }
+
+ /**
+ * Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered.
+ *
+ * @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 IllegalArgumentException
+ * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
+ * @throws IllegalAccessException
+ * if the named field is not {@code public}
+ */
+ public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException {
+ return readDeclaredField(target, fieldName, false);
+ }
+
+ /**
+ * Gets a {@link Field} value by name. Only the class of the specified object will be considered.
+ *
+ * @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 public fields.
+ * @return the Field object
+ * @throws IllegalArgumentException
+ * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
+ * @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);
+ // already forced access above, don't repeat it here:
+ return readField(field, target, false);
+ }
+
+ /**
+ * Writes a {@code public static} {@link Field}.
+ *
+ * @param field
+ * to write
+ * @param value
+ * to set
+ * @throws IllegalArgumentException
+ * if the field is {@code null} or 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 IllegalArgumentException
+ * if the field is {@code null} or 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);
+ }
+
+ /**
+ * Writes a named {@code public static} {@link Field}. Superclasses will be considered.
+ *
+ * @param cls
+ * {@link Class} on which the field is to be found
+ * @param fieldName
+ * to write
+ * @param value
+ * to set
+ * @throws IllegalArgumentException
+ * if {@code cls} is {@code null}, the field name is 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 {@code public} or is {@code final}
+ */
+ public static void writeStaticField(final Class<?> cls, final String fieldName, final Object value) throws IllegalAccessException {
+ writeStaticField(cls, fieldName, value, false);
+ }
+
+ /**
+ * Writes a named {@code static} {@link Field}. Superclasses will be considered.
+ *
+ * @param cls
+ * {@link Class} on which the field is to be found
+ * @param fieldName
+ * 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 IllegalArgumentException
+ * if {@code cls} is {@code null}, the field name is 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 or is {@code final}
+ */
+ public static void writeStaticField(final Class<?> cls, 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);
+ // already forced access above, don't repeat it here:
+ writeStaticField(field, value, false);
+ }
+
+ /**
+ * Writes a named {@code public static} {@link Field}. Only the specified class will be considered.
+ *
+ * @param cls
+ * {@link Class} on which the field is to be found
+ * @param fieldName
+ * to write
+ * @param value
+ * to set
+ * @throws IllegalArgumentException
+ * if {@code cls} is {@code null}, the field name is 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 {@code public} or is {@code final}
+ */
+ public static void writeDeclaredStaticField(final Class<?> cls, final String fieldName, final Object value) throws IllegalAccessException {
+ writeDeclaredStaticField(cls, fieldName, value, false);
+ }
+
+ /**
+ * Writes a named {@code static} {@link Field}. Only the specified class will be considered.
+ *
+ * @param cls
+ * {@link Class} on which the field is to be found
+ * @param fieldName
+ * to write
+ * @param value
+ * to set
+ * @param forceAccess
+ * whether to break scope restrictions using the {@code AccessibleObject#setAccessible(boolean)} method.
+ * {@code false} will only match {@code public} fields.
+ * @throws IllegalArgumentException
+ * if {@code cls} is {@code null}, the field name is 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 or is {@code final}
+ */
+ public static void writeDeclaredStaticField(final Class<?> cls, final String fieldName, final Object value, final boolean forceAccess)
+ throws IllegalAccessException {
+ final Field field = getDeclaredField(cls, fieldName, forceAccess);
+ Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
+ // already forced access above, don't repeat it here:
+ writeField(field, (Object) null, value, false);
+ }
+
+ /**
+ * Writes an accessible {@link Field}.
+ *
+ * @param field
+ * to write
+ * @param target
+ * the object to call on, may be {@code null} for {@code static} fields
+ * @param value
+ * to set
+ * @throws IllegalAccessException
+ * if the field or target is {@code null}, the field is not accessible or is {@code final}, or
+ * {@code value} is not assignable
+ */
+ public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException {
+ writeField(field, target, value, false);
+ }
+
+ /**
+ * Writes a {@link Field}.
+ *
+ * @param field
+ * to write
+ * @param target
+ * the object to call on, may be {@code null} for {@code static} fields
+ * @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 IllegalArgumentException
+ * if the field is {@code null} or {@code value} is not assignable
+ * @throws IllegalAccessException
+ * if the field is not made accessible or is {@code final}
+ */
+ public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess)
+ throws IllegalAccessException {
+ Objects.requireNonNull(field, "field");
+ if (forceAccess && !field.isAccessible()) {
+ field.setAccessible(true);
+ } else {
+ MemberUtils.setAccessibleWorkaround(field);
+ }
+ field.set(target, value);
+ }
+
+ /**
+ * Removes the final modifier from a {@link Field}.
+ *
+ * @param field
+ * to remove the final modifier
+ * @throws IllegalArgumentException
+ * 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 IllegalArgumentException
+ * 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.
+ *
+ * @param target
+ * the object to reflect, must not be {@code null}
+ * @param fieldName
+ * the field name to obtain
+ * @param value
+ * to set
+ * @throws IllegalArgumentException
+ * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
+ * {@code value} is not assignable
+ * @throws IllegalAccessException
+ * if the field is not accessible
+ */
+ public static void writeField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
+ writeField(target, fieldName, value, false);
+ }
+
+ /**
+ * Writes a {@link Field}. Superclasses will be considered.
+ *
+ * @param target
+ * the object to reflect, must not be {@code null}
+ * @param fieldName
+ * 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 IllegalArgumentException
+ * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
+ * {@code value} is not assignable
+ * @throws IllegalAccessException
+ * if the field is not made accessible
+ */
+ public static void writeField(final Object target, final String fieldName, final Object value, final boolean forceAccess)
+ throws IllegalAccessException {
+ Objects.requireNonNull(target, "target");
+ final Class<?> cls = target.getClass();
+ final Field field = getField(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:
+ writeField(field, target, value, false);
+ }
+
+ /**
+ * Writes a {@code public} {@link Field}. Only the specified class will be considered.
+ *
+ * @param target
+ * the object to reflect, must not be {@code null}
+ * @param fieldName
+ * the field name to obtain
+ * @param value
+ * to set
+ * @throws IllegalArgumentException
+ * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
+ * {@code value} is not assignable
+ * @throws IllegalAccessException
+ * if the field is not made accessible
+ */
+ public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
+ writeDeclaredField(target, fieldName, value, false);
+ }
+
+ /**
+ * Writes a {@code public} {@link Field}. Only the specified class will be considered.
+ *
+ * @param target
+ * the object to reflect, must not be {@code null}
+ * @param fieldName
+ * 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 IllegalArgumentException
+ * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
+ * {@code value} is not assignable
+ * @throws IllegalAccessException
+ * if the field is not made accessible
+ */
+ public static void writeDeclaredField(final Object target, 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);
+ // already forced access above, don't repeat it here:
+ writeField(field, target, value, false);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java b/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java
new file mode 100644
index 000000000..3fed00e25
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/InheritanceUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import org.apache.commons.lang3.BooleanUtils;
+
+/**
+ * Utility methods focusing on inheritance.
+ *
+ * @since 3.2
+ */
+public class InheritanceUtils {
+
+ /**
+ * {@link InheritanceUtils} instances should NOT be constructed in standard programming.
+ * Instead, the class should be used as
+ * {@code MethodUtils.getAccessibleMethod(method)}.
+ *
+ * <p>This constructor is {@code public} to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public InheritanceUtils() {
+ }
+
+ /**
+ * Returns the number of inheritance hops between two classes.
+ *
+ * @param child the child class, may be {@code null}
+ * @param parent the parent class, may be {@code null}
+ * @return the number of generations between the child and parent; 0 if the same class;
+ * -1 if the classes are not related as child and parent (includes where either class is null)
+ * @since 3.2
+ */
+ public static int distance(final Class<?> child, final Class<?> parent) {
+ if (child == null || parent == null) {
+ return -1;
+ }
+
+ if (child.equals(parent)) {
+ return 0;
+ }
+
+ final Class<?> cParent = child.getSuperclass();
+ int d = BooleanUtils.toInteger(parent.equals(cParent));
+
+ if (d == 1) {
+ return d;
+ }
+ d += distance(cParent, parent);
+ return d > 0 ? d + 1 : -1;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
new file mode 100644
index 000000000..ba98f13db
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
@@ -0,0 +1,338 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * Contains common code for working with {@link java.lang.reflect.Method Methods}/{@link java.lang.reflect.Constructor Constructors},
+ * extracted and refactored from {@link MethodUtils} when it was imported from Commons BeanUtils.
+ *
+ * @since 2.5
+ */
+final class MemberUtils {
+ // TODO extract an interface to implement compareParameterSets(...)?
+
+ 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 extends AccessibleObject> 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
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" Constructor
+ * @param right the "right" Constructor
+ * @param actual the runtime parameter types to match against
+ * {@code left}/{@code right}
+ * @return int consistent with {@code compare} semantics
+ * @since 3.5
+ */
+ static int compareConstructorFit(final Constructor<?> left, final Constructor<?> right, final Class<?>[] actual) {
+ return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
+ }
+
+ /**
+ * Compares the relative fitness of two Methods in terms of how well they
+ * match a set of runtime parameter types, such that a list ordered
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" Method
+ * @param right the "right" Method
+ * @param actual the runtime parameter types to match against
+ * {@code left}/{@code right}
+ * @return int consistent with {@code compare} semantics
+ * @since 3.5
+ */
+ static int compareMethodFit(final Method left, final Method right, final Class<?>[] actual) {
+ return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
+ }
+
+ /**
+ * Compares the relative fitness of two Executables in terms of how well they
+ * match a set of runtime parameter types, such that a list ordered
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" Executable
+ * @param right the "right" Executable
+ * @param actual the runtime parameter types to match against
+ * {@code left}/{@code right}
+ * @return int consistent with {@code compare} semantics
+ */
+ private static int compareParameterTypes(final Executable left, final Executable right, final Class<?>[] actual) {
+ final float leftCost = getTotalTransformationCost(actual, left);
+ final float rightCost = getTotalTransformationCost(actual, right);
+ 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
+ * hierarchy graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
+ if (destClass.isPrimitive()) {
+ return getPrimitivePromotionCost(srcClass, destClass);
+ }
+ float cost = 0.0f;
+ while (srcClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match,
+ // but
+ // an interface match should override anything where we have to
+ // get a superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ srcClass = srcClass.getSuperclass();
+ }
+ /*
+ * If the destination class is null, we've traveled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (srcClass == null) {
+ cost += 1.5f;
+ }
+ return cost;
+ }
+
+ /**
+ * Gets the number of steps required to promote a primitive number to another
+ * type.
+ * @param srcClass the (primitive) source class
+ * @param destClass the (primitive) destination class
+ * @return The cost of promoting the primitive
+ */
+ private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
+ if (srcClass == null) {
+ return 1.5f;
+ }
+ float cost = 0.0f;
+ Class<?> cls = srcClass;
+ if (!cls.isPrimitive()) {
+ // slight unwrapping penalty
+ cost += 0.1f;
+ cls = ClassUtils.wrapperToPrimitive(cls);
+ }
+ for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
+ if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
+ cost += 0.1f;
+ if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
+ cls = ORDERED_PRIMITIVE_TYPES[i + 1];
+ }
+ }
+ }
+ return cost;
+ }
+
+ static boolean isMatchingMethod(final Method method, final Class<?>[] parameterTypes) {
+ return isMatchingExecutable(Executable.of(method), parameterTypes);
+ }
+
+ static boolean isMatchingConstructor(final Constructor<?> method, final Class<?>[] parameterTypes) {
+ return isMatchingExecutable(Executable.of(method), parameterTypes);
+ }
+
+ private static boolean isMatchingExecutable(final Executable method, final Class<?>[] parameterTypes) {
+ final Class<?>[] methodParameterTypes = method.getParameterTypes();
+ if (ClassUtils.isAssignable(parameterTypes, methodParameterTypes, true)) {
+ return true;
+ }
+
+ if (method.isVarArgs()) {
+ int i;
+ for (i = 0; i < methodParameterTypes.length - 1 && i < parameterTypes.length; i++) {
+ if (!ClassUtils.isAssignable(parameterTypes[i], methodParameterTypes[i], true)) {
+ return false;
+ }
+ }
+ final Class<?> varArgParameterType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
+ for (; i < parameterTypes.length; i++) {
+ if (!ClassUtils.isAssignable(parameterTypes[i], varArgParameterType, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 final Class<?>[] parameterTypes;
+ private final boolean isVarArgs;
+
+ private static Executable of(final Method method) {
+ return new Executable(method);
+ }
+
+ private static Executable of(final Constructor<?> constructor) {
+ return new Executable(constructor);
+ }
+
+ 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;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
new file mode 100644
index 000000000..b70949e57
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
@@ -0,0 +1,1005 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ClassUtils.Interfaces;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
+ * Differences from the BeanUtils version may be noted, especially where similar functionality
+ * already existed within Lang.
+ *
+ * <h2>Known Limitations</h2>
+ * <h3>Accessing Public Methods In A Default Access Superclass</h3>
+ * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
+ * Reflection locates these methods fine and correctly assigns them as {@code public}.
+ * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
+ *
+ * <p>{@link MethodUtils} contains a workaround for this situation.
+ * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privileges.
+ * If this call fails then the method may fail.</p>
+ *
+ * @since 2.5
+ */
+public class MethodUtils {
+
+ private static final Comparator<Method> 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)}.
+ *
+ * <p>This constructor is {@code public} to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public MethodUtils() {
+ }
+
+ /**
+ * Invokes a named method without parameters.
+ *
+ * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * <p>This is a convenient wrapper for
+ * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>This is a convenient wrapper for
+ * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>This is a convenient wrapper for
+ * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * @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
+ */
+ 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.
+ *
+ * <p>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.</p>
+ *
+ * <p>This is a convenient wrapper for
+ * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * @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
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @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
+ * @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<? extends Object> 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);
+ }
+
+ 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.
+ *
+ * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod}(Class, String, Class[])}.</p>
+ *
+ * @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.
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod}(Class, String, Class[])}.</p>
+ *
+ * @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
+ */
+ 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.
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @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
+ */
+ 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.
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @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.
+ *
+ * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class, String, Object[], Class[])}.
+ * </p>
+ *
+ * @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.
+ *
+ * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @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));
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return {@code null}.
+ * This is just a convenience wrapper for
+ * {@link #getAccessibleMethod(Method)}.
+ *
+ * @param cls get method from this class
+ * @param methodName get method with this name
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(final Class<?> cls, final String methodName,
+ final Class<?>... parameterTypes) {
+ try {
+ return getAccessibleMethod(cls.getMethod(methodName, parameterTypes));
+ } catch (final NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return {@code null}.
+ *
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Method method) {
+ if (!MemberUtils.isAccessible(method)) {
+ return null;
+ }
+ // If the declaring class is public, we are done
+ final Class<?> cls = method.getDeclaringClass();
+ if (ClassUtils.isPublic(cls)) {
+ return method;
+ }
+ final String methodName = method.getName();
+ final Class<?>[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method = getAccessibleMethodFromInterfaceNest(cls, methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(cls, methodName,
+ parameterTypes);
+ }
+ 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
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return {@code null}.
+ *
+ * <p>There isn't any good reason why this method must be {@code private}.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.</p>
+ *
+ * @param cls Parent class for the interfaces 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 getAccessibleMethodFromInterfaceNest(Class<?> cls,
+ final String methodName, final Class<?>... parameterTypes) {
+ // Search up the superclass chain
+ for (; cls != null; cls = cls.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ final Class<?>[] interfaces = cls.getInterfaces();
+ for (final Class<?> anInterface : interfaces) {
+ // Is this interface public?
+ if (!ClassUtils.isPublic(anInterface)) {
+ continue;
+ }
+ // Does the method exist on this interface?
+ try {
+ return anInterface.getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (final NoSuchMethodException ignored) {
+ /*
+ * Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ // Recursively check our parent interfaces
+ final Method method = getAccessibleMethodFromInterfaceNest(anInterface,
+ methodName, parameterTypes);
+ if (method != null) {
+ return method;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.
+ *
+ * <p>This method is used by
+ * {@link
+ * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+ * </p>
+ *
+ * <p>This method can match primitive parameter by passing in wrapper classes.
+ * For example, a {@link Boolean} will match a primitive {@code boolean}
+ * parameter.
+ * </p>
+ *
+ * @param cls find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with most compatible parameters
+ * @return The accessible method
+ */
+ public static Method getMatchingAccessibleMethod(final Class<?> cls,
+ final String methodName, final Class<?>... parameterTypes) {
+ try {
+ return MemberUtils.setAccessibleWorkaround(cls.getMethod(methodName, parameterTypes));
+ } catch (final NoSuchMethodException ignored) {
+ // Swallow the exception
+ }
+ // search through all methods
+ final Method[] methods = cls.getMethods();
+ final List<Method> matchingMethods = Stream.of(methods)
+ .filter(method -> method.getName().equals(methodName) && MemberUtils.isMatchingMethod(method, parameterTypes)).collect(Collectors.toList());
+
+ // Sort methods by signature to force deterministic result
+ matchingMethods.sort(METHOD_BY_SIGNATURE);
+
+ Method bestMatch = null;
+ for (final Method method : matchingMethods) {
+ // get accessible version of method
+ final Method accessibleMethod = getAccessibleMethod(method);
+ if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(accessibleMethod, bestMatch, parameterTypes) < 0)) {
+ bestMatch = accessibleMethod;
+ }
+ }
+ if (bestMatch != null) {
+ MemberUtils.setAccessibleWorkaround(bestMatch);
+ }
+
+ if (bestMatch != null && bestMatch.isVarArgs() && bestMatch.getParameterTypes().length > 0 && parameterTypes.length > 0) {
+ final Class<?>[] methodParameterTypes = bestMatch.getParameterTypes();
+ final Class<?> methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
+ final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName();
+
+ final Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1];
+ final String parameterTypeName = lastParameterType == null ? null : lastParameterType.getName();
+ final String parameterTypeSuperClassName = lastParameterType == null ? null : lastParameterType.getSuperclass().getName();
+
+ if (parameterTypeName != null && parameterTypeSuperClassName != null && !methodParameterComponentTypeName.equals(parameterTypeName)
+ && !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) {
+ return null;
+ }
+ }
+
+ return bestMatch;
+ }
+
+ /**
+ * Retrieves a method whether or not it's accessible. If no such method
+ * can be found, return {@code null}.
+ * @param cls The class that will be subjected to the method search
+ * @param methodName The method that we wish to call
+ * @param parameterTypes Argument class types
+ * @throws IllegalStateException if there is no unique result
+ * @return The method
+ *
+ * @since 3.5
+ */
+ public static Method getMatchingMethod(final Class<?> cls, final String methodName,
+ final Class<?>... parameterTypes) {
+ Objects.requireNonNull(cls, "cls");
+ Validate.notEmpty(methodName, "methodName");
+
+ final List<Method> methods = Stream.of(cls.getDeclaredMethods())
+ .filter(method -> method.getName().equals(methodName))
+ .collect(Collectors.toList());
+
+ ClassUtils.getAllSuperclasses(cls).stream()
+ .map(Class::getDeclaredMethods)
+ .flatMap(Stream::of)
+ .filter(method -> method.getName().equals(methodName))
+ .forEach(methods::add);
+
+ for (final Method method : methods) {
+ if (Arrays.deepEquals(method.getParameterTypes(), parameterTypes)) {
+ return method;
+ }
+ }
+
+ final TreeMap<Integer, List<Method>> candidates = new TreeMap<>();
+
+ methods.stream()
+ .filter(method -> ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true))
+ .forEach(method -> {
+ final int distance = distance(parameterTypes, method.getParameterTypes());
+ final List<Method> candidatesAtDistance = candidates.computeIfAbsent(distance, k -> new ArrayList<>());
+ candidatesAtDistance.add(method);
+ });
+
+ if (candidates.isEmpty()) {
+ return null;
+ }
+
+ final List<Method> bestCandidates = candidates.values().iterator().next();
+ if (bestCandidates.size() == 1) {
+ return bestCandidates.get(0);
+ }
+
+ throw new IllegalStateException(
+ String.format("Found multiple candidates for method %s on class %s : %s",
+ methodName + Stream.of(parameterTypes).map(String::valueOf).collect(Collectors.joining(",", "(", ")")),
+ cls.getName(),
+ bestCandidates.stream().map(Method::toString).collect(Collectors.joining(",", "[", "]")))
+ );
+ }
+
+ /**
+ * 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.
+ */
+ private static int distance(final Class<?>[] fromClassArray, final Class<?>[] toClassArray) {
+ int answer = 0;
+
+ 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;
+ }
+ }
+
+ return answer;
+ }
+
+ /**
+ * Gets the hierarchy of overridden methods down to {@code result} respecting generics.
+ * @param method lowest to consider
+ * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false
+ * @return Set&lt;Method&gt; in ascending order from sub- to superclass
+ * @throws NullPointerException if the specified method is {@code null}
+ * @since 3.2
+ */
+ public static Set<Method> getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior) {
+ Objects.requireNonNull(method, "method");
+ final Set<Method> result = new LinkedHashSet<>();
+ result.add(method);
+
+ final Class<?>[] parameterTypes = method.getParameterTypes();
+
+ final Class<?> declaringClass = method.getDeclaringClass();
+
+ final Iterator<Class<?>> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator();
+ //skip the declaring class :P
+ hierarchy.next();
+ hierarchyTraversal: while (hierarchy.hasNext()) {
+ final Class<?> c = hierarchy.next();
+ final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes);
+ if (m == null) {
+ continue;
+ }
+ if (Arrays.equals(m.getParameterTypes(), parameterTypes)) {
+ // matches without generics
+ result.add(m);
+ continue;
+ }
+ // necessary to get arguments every time in the case that we are including interfaces
+ final Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass());
+ for (int i = 0; i < parameterTypes.length; i++) {
+ final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]);
+ final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]);
+ if (!TypeUtils.equals(childType, parentType)) {
+ continue hierarchyTraversal;
+ }
+ }
+ result.add(m);
+ }
+ return result;
+ }
+
+ /**
+ * 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<? extends Annotation> annotationCls) {
+ return getMethodsWithAnnotation(cls, annotationCls, false, false);
+ }
+
+ /**
+ * 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 IllegalArgumentException
+ * if the class or annotation are {@code null}
+ * @since 3.4
+ */
+ public static List<Method> getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> 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<? extends Annotation> 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<Method> getMethodsListWithAnnotation(final Class<?> cls,
+ final Class<? extends Annotation> annotationCls,
+ final boolean searchSupers, final boolean ignoreAccess) {
+
+ Objects.requireNonNull(cls, "cls");
+ Objects.requireNonNull(annotationCls, "annotationCls");
+ final List<Class<?>> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>();
+ classes.add(0, cls);
+ final List<Method> 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.
+ *
+ * <p>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.</p>
+ * @param <A>
+ * 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 extends Annotation> A getAnnotation(final Method method, final Class<A> 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<Class<?>> 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;
+ }
+
+ /**
+ * 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<Class<?>> getAllSuperclassesAndInterfaces(final Class<?> cls) {
+ if (cls == null) {
+ return null;
+ }
+
+ final List<Class<?>> allSuperClassesAndInterfaces = new ArrayList<>();
+ final List<Class<?>> allSuperclasses = ClassUtils.getAllSuperclasses(cls);
+ int superClassIndex = 0;
+ final List<Class<?>> 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;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java b/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java
new file mode 100644
index 000000000..e3ea475e7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeLiteral.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Type literal comparable to {@code javax.enterprise.util.TypeLiteral},
+ * made generally available outside the JEE context. Allows the passing around of
+ * a "token" that represents a type in a typesafe manner, as opposed to
+ * passing the (non-parameterized) {@link Type} object itself. Consider:
+ * <p>
+ * You might see such a typesafe API as:
+ * <pre>
+ * class Typesafe {
+ * &lt;T&gt; T obtain(Class&lt;T&gt; type, ...);
+ * }
+ * </pre>
+ * Consumed in the manner of:
+ * <pre>
+ * Foo foo = typesafe.obtain(Foo.class, ...);
+ * </pre>
+ * Yet, you run into problems when you want to do this with a parameterized type:
+ * <pre>
+ * List&lt;String&gt; listOfString = typesafe.obtain(List.class, ...); // could only give us a raw List
+ * </pre>
+ * {@code java.lang.reflect.Type} might provide some value:
+ * <pre>
+ * Type listOfStringType = ...; // firstly, how to obtain this? Doable, but not straightforward.
+ * List&lt;String&gt; listOfString = (List&lt;String&gt;) typesafe.obtain(listOfStringType, ...); // nongeneric Type would necessitate a cast
+ * </pre>
+ * The "type literal" concept was introduced to provide an alternative, i.e.:
+ * <pre>
+ * class Typesafe {
+ * &lt;T&gt; T obtain(TypeLiteral&lt;T&gt; type, ...);
+ * }
+ * </pre>
+ * Consuming code looks like:
+ * <pre>
+ * List&lt;String&gt; listOfString = typesafe.obtain(new TypeLiteral&lt;List&lt;String&gt;&gt;() {}, ...);
+ * </pre>
+ * <p>
+ * This has the effect of "jumping up" a level to tie a {@code java.lang.reflect.Type}
+ * to a type variable while simultaneously making it short work to obtain a
+ * {@link Type} instance for any given type, inline.
+ * </p>
+ * <p>Additionally {@link TypeLiteral} implements the {@link Typed} interface which
+ * is a generalization of this concept, and which may be implemented in custom classes.
+ * It is suggested that APIs be defined in terms of the interface, in the following manner:
+ * </p>
+ * <pre>
+ * &lt;T&gt; T obtain(Typed&lt;T&gt; typed, ...);
+ * </pre>
+ *
+ * @param <T> the type
+ * @since 3.2
+ */
+public abstract class TypeLiteral<T> implements Typed<T> {
+
+ @SuppressWarnings("rawtypes")
+ private static final TypeVariable<Class<TypeLiteral>> T = TypeLiteral.class.getTypeParameters()[0];
+
+ /**
+ * Represented type.
+ */
+ public final Type value;
+
+ private final String toString;
+
+ /**
+ * The default constructor.
+ */
+ protected TypeLiteral() {
+ this.value =
+ Validate.notNull(TypeUtils.getTypeArguments(getClass(), TypeLiteral.class).get(T),
+ "%s does not assign type parameter %s", getClass(), TypeUtils.toLongString(T));
+
+ this.toString = String.format("%s<%s>", TypeLiteral.class.getSimpleName(), TypeUtils.toString(value));
+ }
+
+ @Override
+ public final boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof TypeLiteral)) {
+ return false;
+ }
+ final TypeLiteral<?> other = (TypeLiteral<?>) obj;
+ return TypeUtils.equals(value, other.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return 37 << 4 | value.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toString;
+ }
+
+ @Override
+ public Type getType() {
+ return value;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
new file mode 100644
index 000000000..83beab013
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -0,0 +1,1937 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.Builder;
+
+/**
+ * Utility methods focusing on type inspection, particularly with regard to
+ * generics.
+ *
+ * @since 3.0
+ */
+public class TypeUtils {
+
+ /**
+ * GenericArrayType implementation class.
+ * @since 3.2
+ */
+ private static final class GenericArrayTypeImpl implements GenericArrayType {
+ private final Type componentType;
+
+ /**
+ * Constructor
+ * @param componentType of this array type
+ */
+ private GenericArrayTypeImpl(final Type componentType) {
+ this.componentType = componentType;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type getGenericComponentType() {
+ return componentType;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ int result = 67 << 4;
+ result |= componentType.hashCode();
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+ }
+
+ /**
+ * ParameterizedType implementation class.
+ * @since 3.2
+ */
+ private static final class ParameterizedTypeImpl implements ParameterizedType {
+ private final Class<?> raw;
+ private final Type useOwner;
+ private final Type[] typeArguments;
+
+ /**
+ * Constructor
+ * @param rawClass type
+ * @param useOwner owner type to use, if any
+ * @param typeArguments formal type arguments
+ */
+ private ParameterizedTypeImpl(final Class<?> rawClass, final Type useOwner, final Type[] typeArguments) {
+ this.raw = rawClass;
+ this.useOwner = useOwner;
+ this.typeArguments = Arrays.copyOf(typeArguments, typeArguments.length, Type[].class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, (ParameterizedType) obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type[] getActualTypeArguments() {
+ return typeArguments.clone();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type getOwnerType() {
+ return useOwner;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type getRawType() {
+ return raw;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ int result = 71 << 4;
+ result |= raw.hashCode();
+ result <<= 4;
+ result |= Objects.hashCode(useOwner);
+ result <<= 8;
+ result |= Arrays.hashCode(typeArguments);
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+ }
+
+ /**
+ * {@link WildcardType} builder.
+ * @since 3.2
+ */
+ public static class WildcardTypeBuilder implements Builder<WildcardType> {
+ private Type[] upperBounds;
+
+ private Type[] lowerBounds;
+ /**
+ * Constructor
+ */
+ private WildcardTypeBuilder() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WildcardType build() {
+ return new WildcardTypeImpl(upperBounds, lowerBounds);
+ }
+
+ /**
+ * Specify lower bounds of the wildcard type to build.
+ * @param bounds to set
+ * @return {@code this}
+ */
+ public WildcardTypeBuilder withLowerBounds(final Type... bounds) {
+ this.lowerBounds = bounds;
+ return this;
+ }
+
+ /**
+ * Specify upper bounds of the wildcard type to build.
+ * @param bounds to set
+ * @return {@code this}
+ */
+ public WildcardTypeBuilder withUpperBounds(final Type... bounds) {
+ this.upperBounds = bounds;
+ return this;
+ }
+ }
+
+ /**
+ * WildcardType implementation class.
+ * @since 3.2
+ */
+ private static final class WildcardTypeImpl implements WildcardType {
+ private final Type[] upperBounds;
+ private final Type[] lowerBounds;
+
+ /**
+ * Constructor
+ * @param upperBounds of this type
+ * @param lowerBounds of this type
+ */
+ private WildcardTypeImpl(final Type[] upperBounds, final Type[] lowerBounds) {
+ this.upperBounds = ObjectUtils.defaultIfNull(upperBounds, ArrayUtils.EMPTY_TYPE_ARRAY);
+ this.lowerBounds = ObjectUtils.defaultIfNull(lowerBounds, ArrayUtils.EMPTY_TYPE_ARRAY);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type[] getLowerBounds() {
+ return lowerBounds.clone();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Type[] getUpperBounds() {
+ return upperBounds.clone();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ int result = 73 << 8;
+ result |= Arrays.hashCode(upperBounds);
+ result <<= 8;
+ result |= Arrays.hashCode(lowerBounds);
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+ }
+
+ /**
+ * A wildcard instance matching {@code ?}.
+ * @since 3.2
+ */
+ public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build();
+
+ /**
+ * Appends {@code types} to {@code builder} with separator {@code sep}.
+ *
+ * @param builder destination
+ * @param sep separator
+ * @param types to append
+ * @return {@code builder}
+ * @since 3.2
+ */
+ private static <T> StringBuilder appendAllTo(final StringBuilder builder, final String sep,
+ @SuppressWarnings("unchecked") final T... types) {
+ Validate.notEmpty(Validate.noNullElements(types));
+ if (types.length > 0) {
+ builder.append(toString(types[0]));
+ for (int i = 1; i < types.length; i++) {
+ builder.append(sep).append(toString(types[i]));
+ }
+ }
+ return builder;
+ }
+
+ private static void appendRecursiveTypes(final StringBuilder builder, final int[] recursiveTypeIndexes,
+ final Type[] argumentTypes) {
+ for (int i = 0; i < recursiveTypeIndexes.length; i++) {
+ appendAllTo(builder.append('<'), ", ", argumentTypes[i].toString()).append('>');
+ }
+
+ final Type[] argumentsFiltered = ArrayUtils.removeAll(argumentTypes, recursiveTypeIndexes);
+
+ if (argumentsFiltered.length > 0) {
+ appendAllTo(builder.append('<'), ", ", argumentsFiltered).append('>');
+ }
+ }
+
+ /**
+ * Formats a {@link Class} as a {@link String}.
+ *
+ * @param cls {@link Class} to format
+ * @return String
+ * @since 3.2
+ */
+ private static String classToString(final Class<?> cls) {
+ if (cls.isArray()) {
+ return toString(cls.getComponentType()) + "[]";
+ }
+
+ final StringBuilder buf = new StringBuilder();
+
+ if (cls.getEnclosingClass() != null) {
+ buf.append(classToString(cls.getEnclosingClass())).append('.').append(cls.getSimpleName());
+ } else {
+ buf.append(cls.getName());
+ }
+ if (cls.getTypeParameters().length > 0) {
+ buf.append('<');
+ appendAllTo(buf, ", ", cls.getTypeParameters());
+ buf.append('>');
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Tests, recursively, whether any of the type parameters associated with {@code type} are bound to variables.
+ *
+ * @param type the type to check for type variables
+ * @return boolean
+ * @since 3.2
+ */
+ public static boolean containsTypeVariables(final Type type) {
+ if (type instanceof TypeVariable<?>) {
+ return true;
+ }
+ if (type instanceof Class<?>) {
+ return ((Class<?>) type).getTypeParameters().length > 0;
+ }
+ if (type instanceof ParameterizedType) {
+ for (final Type arg : ((ParameterizedType) type).getActualTypeArguments()) {
+ if (containsTypeVariables(arg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (type instanceof WildcardType) {
+ final WildcardType wild = (WildcardType) type;
+ return containsTypeVariables(getImplicitLowerBounds(wild)[0])
+ || containsTypeVariables(getImplicitUpperBounds(wild)[0]);
+ }
+ if (type instanceof GenericArrayType) {
+ return containsTypeVariables(((GenericArrayType) type).getGenericComponentType());
+ }
+ return false;
+ }
+
+ private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable<?> typeVariable,
+ final ParameterizedType parameterizedType) {
+ return ArrayUtils.contains(typeVariable.getBounds(), parameterizedType);
+ }
+
+ /**
+ * Tries to determine the type arguments of a class/interface based on a
+ * super parameterized type's type arguments. This method is the inverse of
+ * {@link #getTypeArguments(Type, Class)} which gets a class/interface's
+ * type arguments based on a subtype. It is far more limited in determining
+ * the type arguments for the subject class's type variables in that it can
+ * only determine those parameters that map from the subject {@link Class}
+ * object to the supertype.
+ *
+ * <p>
+ * Example: {@link java.util.TreeSet
+ * TreeSet} sets its parameter as the parameter for
+ * {@link java.util.NavigableSet NavigableSet}, which in turn sets the
+ * parameter of {@link java.util.SortedSet}, which in turn sets the
+ * parameter of {@link Set}, which in turn sets the parameter of
+ * {@link java.util.Collection}, which in turn sets the parameter of
+ * {@link Iterable}. Since {@link TreeSet}'s parameter maps
+ * (indirectly) to {@link Iterable}'s parameter, it will be able to
+ * determine that based on the super type {@code Iterable<? extends
+ * Map<Integer, ? extends Collection<?>>>}, the parameter of
+ * {@link TreeSet} is {@code ? extends Map<Integer, ? extends
+ * Collection<?>>}.
+ * </p>
+ *
+ * @param cls the class whose type parameters are to be determined, not {@code null}
+ * @param superParameterizedType the super type from which {@code cls}'s type
+ * arguments are to be determined, not {@code null}
+ * @return a {@link Map} of the type assignments that could be determined
+ * for the type variables in each type in the inheritance hierarchy from
+ * {@code type} to {@code toClass} inclusive.
+ */
+ public static Map<TypeVariable<?>, Type> determineTypeArguments(final Class<?> cls,
+ final ParameterizedType superParameterizedType) {
+ Objects.requireNonNull(cls, "cls");
+ Objects.requireNonNull(superParameterizedType, "superParameterizedType");
+
+ final Class<?> superClass = getRawType(superParameterizedType);
+
+ // compatibility check
+ if (!isAssignable(cls, superClass)) {
+ return null;
+ }
+
+ if (cls.equals(superClass)) {
+ return getTypeArguments(superParameterizedType, superClass, null);
+ }
+
+ // get the next class in the inheritance hierarchy
+ final Type midType = getClosestParentType(cls, superClass);
+
+ // can only be a class or a parameterized type
+ if (midType instanceof Class<?>) {
+ return determineTypeArguments((Class<?>) midType, superParameterizedType);
+ }
+
+ final ParameterizedType midParameterizedType = (ParameterizedType) midType;
+ final Class<?> midClass = getRawType(midParameterizedType);
+ // get the type variables of the mid class that map to the type
+ // arguments of the super class
+ final Map<TypeVariable<?>, Type> typeVarAssigns = determineTypeArguments(midClass, superParameterizedType);
+ // map the arguments of the mid type to the class type variables
+ mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns);
+
+ return typeVarAssigns;
+ }
+
+ /**
+ * Tests whether {@code t} equals {@code a}.
+ *
+ * @param genericArrayType LHS
+ * @param type RHS
+ * @return boolean
+ * @since 3.2
+ */
+ private static boolean equals(final GenericArrayType genericArrayType, final Type type) {
+ return type instanceof GenericArrayType
+ && equals(genericArrayType.getGenericComponentType(), ((GenericArrayType) type).getGenericComponentType());
+ }
+
+ /**
+ * Tests whether {@code t} equals {@code p}.
+ *
+ * @param parameterizedType LHS
+ * @param type RHS
+ * @return boolean
+ * @since 3.2
+ */
+ private static boolean equals(final ParameterizedType parameterizedType, final Type type) {
+ if (type instanceof ParameterizedType) {
+ final ParameterizedType other = (ParameterizedType) type;
+ if (equals(parameterizedType.getRawType(), other.getRawType())
+ && equals(parameterizedType.getOwnerType(), other.getOwnerType())) {
+ return equals(parameterizedType.getActualTypeArguments(), other.getActualTypeArguments());
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests equality of types.
+ *
+ * @param type1 the first type
+ * @param type2 the second type
+ * @return boolean
+ * @since 3.2
+ */
+ public static boolean equals(final Type type1, final Type type2) {
+ if (Objects.equals(type1, type2)) {
+ return true;
+ }
+ if (type1 instanceof ParameterizedType) {
+ return equals((ParameterizedType) type1, type2);
+ }
+ if (type1 instanceof GenericArrayType) {
+ return equals((GenericArrayType) type1, type2);
+ }
+ if (type1 instanceof WildcardType) {
+ return equals((WildcardType) type1, type2);
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether {@code t1} equals {@code t2}.
+ *
+ * @param type1 LHS
+ * @param type2 RHS
+ * @return boolean
+ * @since 3.2
+ */
+ private static boolean equals(final Type[] type1, final Type[] type2) {
+ if (type1.length == type2.length) {
+ for (int i = 0; i < type1.length; i++) {
+ if (!equals(type1[i], type2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether {@code t} equals {@code w}.
+ *
+ * @param wildcardType LHS
+ * @param type RHS
+ * @return boolean
+ * @since 3.2
+ */
+ private static boolean equals(final WildcardType wildcardType, final Type type) {
+ if (type instanceof WildcardType) {
+ final WildcardType other = (WildcardType) type;
+ return equals(getImplicitLowerBounds(wildcardType), getImplicitLowerBounds(other))
+ && equals(getImplicitUpperBounds(wildcardType), getImplicitUpperBounds(other));
+ }
+ return false;
+ }
+
+ /**
+ * Helper method to establish the formal parameters for a parameterized type.
+ *
+ * @param mappings map containing the assignments
+ * @param variables expected map keys
+ * @return array of map values corresponding to specified keys
+ */
+ private static Type[] extractTypeArgumentsFrom(final Map<TypeVariable<?>, Type> mappings, final TypeVariable<?>[] variables) {
+ final Type[] result = new Type[variables.length];
+ int index = 0;
+ for (final TypeVariable<?> var : variables) {
+ Validate.isTrue(mappings.containsKey(var), "missing argument mapping for %s", toString(var));
+ result[index++] = mappings.get(var);
+ }
+ return result;
+ }
+
+ private static int[] findRecursiveTypes(final ParameterizedType parameterizedType) {
+ final Type[] filteredArgumentTypes = Arrays.copyOf(parameterizedType.getActualTypeArguments(),
+ parameterizedType.getActualTypeArguments().length);
+ int[] indexesToRemove = {};
+ for (int i = 0; i < filteredArgumentTypes.length; i++) {
+ if (filteredArgumentTypes[i] instanceof TypeVariable<?> && containsVariableTypeSameParametrizedTypeBound(
+ (TypeVariable<?>) filteredArgumentTypes[i], parameterizedType)) {
+ indexesToRemove = ArrayUtils.add(indexesToRemove, i);
+ }
+ }
+ return indexesToRemove;
+ }
+
+ /**
+ * Creates a generic array type instance.
+ *
+ * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]}
+ * is {@code boolean}
+ * @return {@link GenericArrayType}
+ * @since 3.2
+ */
+ public static GenericArrayType genericArrayType(final Type componentType) {
+ return new GenericArrayTypeImpl(Objects.requireNonNull(componentType, "componentType"));
+ }
+
+ /**
+ * Formats a {@link GenericArrayType} as a {@link String}.
+ *
+ * @param genericArrayType {@link GenericArrayType} to format
+ * @return String
+ * @since 3.2
+ */
+ private static String genericArrayTypeToString(final GenericArrayType genericArrayType) {
+ return String.format("%s[]", toString(genericArrayType.getGenericComponentType()));
+ }
+
+ /**
+ * Gets the array component type of {@code type}.
+ *
+ * @param type the type to be checked
+ * @return component type or null if type is not an array type
+ */
+ public static Type getArrayComponentType(final Type type) {
+ if (type instanceof Class<?>) {
+ final Class<?> cls = (Class<?>) type;
+ return cls.isArray() ? cls.getComponentType() : null;
+ }
+ if (type instanceof GenericArrayType) {
+ return ((GenericArrayType) type).getGenericComponentType();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the closest parent type to the
+ * super class specified by {@code superClass}.
+ *
+ * @param cls the class in question
+ * @param superClass the super class
+ * @return the closes parent type
+ */
+ private static Type getClosestParentType(final Class<?> cls, final Class<?> superClass) {
+ // only look at the interfaces if the super class is also an interface
+ if (superClass.isInterface()) {
+ // get the generic interfaces of the subject class
+ final Type[] interfaceTypes = cls.getGenericInterfaces();
+ // will hold the best generic interface match found
+ Type genericInterface = null;
+
+ // find the interface closest to the super class
+ for (final Type midType : interfaceTypes) {
+ final Class<?> midClass;
+
+ if (midType instanceof ParameterizedType) {
+ midClass = getRawType((ParameterizedType) midType);
+ } else if (midType instanceof Class<?>) {
+ midClass = (Class<?>) midType;
+ } else {
+ throw new IllegalStateException("Unexpected generic"
+ + " interface type found: " + midType);
+ }
+
+ // check if this interface is further up the inheritance chain
+ // than the previously found match
+ if (isAssignable(midClass, superClass)
+ && isAssignable(genericInterface, (Type) midClass)) {
+ genericInterface = midType;
+ }
+ }
+
+ // found a match?
+ if (genericInterface != null) {
+ return genericInterface;
+ }
+ }
+
+ // none of the interfaces were descendants of the target class, so the
+ // super class has to be one, instead
+ return cls.getGenericSuperclass();
+ }
+
+ /**
+ * Gets an array containing the sole type of {@link Object} if
+ * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it
+ * returns the result of {@link TypeVariable#getBounds()} passed into
+ * {@link #normalizeUpperBounds}.
+ *
+ * @param typeVariable the subject type variable, not {@code null}
+ * @return a non-empty array containing the bounds of the type variable.
+ */
+ public static Type[] getImplicitBounds(final TypeVariable<?> typeVariable) {
+ Objects.requireNonNull(typeVariable, "typeVariable");
+ final Type[] bounds = typeVariable.getBounds();
+
+ return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
+ }
+
+ /**
+ * Gets an array containing a single value of {@code null} if
+ * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise,
+ * it returns the result of {@link WildcardType#getLowerBounds()}.
+ *
+ * @param wildcardType the subject wildcard type, not {@code null}
+ * @return a non-empty array containing the lower bounds of the wildcard
+ * type.
+ */
+ public static Type[] getImplicitLowerBounds(final WildcardType wildcardType) {
+ Objects.requireNonNull(wildcardType, "wildcardType");
+ final Type[] bounds = wildcardType.getLowerBounds();
+
+ return bounds.length == 0 ? new Type[] { null } : bounds;
+ }
+
+ /**
+ * Gets an array containing the sole value of {@link Object} if
+ * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise,
+ * it returns the result of {@link WildcardType#getUpperBounds()}
+ * passed into {@link #normalizeUpperBounds}.
+ *
+ * @param wildcardType the subject wildcard type, not {@code null}
+ * @return a non-empty array containing the upper bounds of the wildcard
+ * type.
+ */
+ public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) {
+ Objects.requireNonNull(wildcardType, "wildcardType");
+ final Type[] bounds = wildcardType.getUpperBounds();
+
+ return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
+ }
+
+ /**
+ * Transforms the passed in type to a {@link Class} object. Type-checking method of convenience.
+ *
+ * @param parameterizedType the type to be converted
+ * @return the corresponding {@link Class} object
+ * @throws IllegalStateException if the conversion fails
+ */
+ private static Class<?> getRawType(final ParameterizedType parameterizedType) {
+ final Type rawType = parameterizedType.getRawType();
+
+ // check if raw type is a Class object
+ // not currently necessary, but since the return type is Type instead of
+ // Class, there's enough reason to believe that future versions of Java
+ // may return other Type implementations. And type-safety checking is
+ // rarely a bad idea.
+ if (!(rawType instanceof Class<?>)) {
+ throw new IllegalStateException("Wait... What!? Type of rawType: " + rawType);
+ }
+
+ return (Class<?>) rawType;
+ }
+
+ /**
+ * Gets the raw type of a Java type, given its context. Primarily for use
+ * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do
+ * not know the runtime type of {@code type}: if you know you have a
+ * {@link Class} instance, it is already raw; if you know you have a
+ * {@link ParameterizedType}, its raw type is only a method call away.
+ *
+ * @param type to resolve
+ * @param assigningType type to be resolved against
+ * @return the resolved {@link Class} object or {@code null} if
+ * the type could not be resolved
+ */
+ public static Class<?> getRawType(final Type type, final Type assigningType) {
+ if (type instanceof Class<?>) {
+ // it is raw, no problem
+ return (Class<?>) type;
+ }
+
+ if (type instanceof ParameterizedType) {
+ // simple enough to get the raw type of a ParameterizedType
+ return getRawType((ParameterizedType) type);
+ }
+
+ if (type instanceof TypeVariable<?>) {
+ if (assigningType == null) {
+ return null;
+ }
+
+ // get the entity declaring this type variable
+ final Object genericDeclaration = ((TypeVariable<?>) type).getGenericDeclaration();
+
+ // can't get the raw type of a method- or constructor-declared type
+ // variable
+ if (!(genericDeclaration instanceof Class<?>)) {
+ return null;
+ }
+
+ // get the type arguments for the declaring class/interface based
+ // on the enclosing type
+ final Map<TypeVariable<?>, Type> typeVarAssigns = getTypeArguments(assigningType,
+ (Class<?>) genericDeclaration);
+
+ // enclosingType has to be a subclass (or subinterface) of the
+ // declaring type
+ if (typeVarAssigns == null) {
+ return null;
+ }
+
+ // get the argument assigned to this type variable
+ final Type typeArgument = typeVarAssigns.get(type);
+
+ if (typeArgument == null) {
+ return null;
+ }
+
+ // get the argument for this type variable
+ return getRawType(typeArgument, assigningType);
+ }
+
+ if (type instanceof GenericArrayType) {
+ // get raw component type
+ final Class<?> rawComponentType = getRawType(((GenericArrayType) type)
+ .getGenericComponentType(), assigningType);
+
+ // create array type from raw component type and return its class
+ return Array.newInstance(rawComponentType, 0).getClass();
+ }
+
+ // (hand-waving) this is not the method you're looking for
+ if (type instanceof WildcardType) {
+ return null;
+ }
+
+ throw new IllegalArgumentException("unknown type: " + type);
+ }
+
+ /**
+ * Gets a map of the type arguments of a class in the context of {@code toClass}.
+ *
+ * @param cls the class in question
+ * @param toClass the context class
+ * @param subtypeVarAssigns a map with type variables
+ * @return the {@link Map} with type arguments
+ */
+ private static Map<TypeVariable<?>, Type> getTypeArguments(Class<?> cls, final Class<?> toClass,
+ final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
+ // make sure they're assignable
+ if (!isAssignable(cls, toClass)) {
+ return null;
+ }
+
+ // can't work with primitives
+ if (cls.isPrimitive()) {
+ // both classes are primitives?
+ if (toClass.isPrimitive()) {
+ // dealing with widening here. No type arguments to be
+ // harvested with these two types.
+ return new HashMap<>();
+ }
+
+ // work with wrapper the wrapper class instead of the primitive
+ cls = ClassUtils.primitiveToWrapper(cls);
+ }
+
+ // create a copy of the incoming map, or an empty one if it's null
+ final HashMap<TypeVariable<?>, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>()
+ : new HashMap<>(subtypeVarAssigns);
+
+ // has target class been reached?
+ if (toClass.equals(cls)) {
+ return typeVarAssigns;
+ }
+
+ // walk the inheritance hierarchy until the target class is reached
+ return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns);
+ }
+
+ /**
+ * Gets all the type arguments for this parameterized type
+ * including owner hierarchy arguments such as
+ * {@code Outer<K, V>.Inner<T>.DeepInner<E>} .
+ * The arguments are returned in a
+ * {@link Map} specifying the argument type for each {@link TypeVariable}.
+ *
+ * @param type specifies the subject parameterized type from which to
+ * harvest the parameters.
+ * @return a {@link Map} of the type arguments to their respective type
+ * variables.
+ */
+ public static Map<TypeVariable<?>, Type> getTypeArguments(final ParameterizedType type) {
+ return getTypeArguments(type, getRawType(type), null);
+ }
+
+ /**
+ * Gets a map of the type arguments of a parameterized type in the context of {@code toClass}.
+ *
+ * @param parameterizedType the parameterized type
+ * @param toClass the class
+ * @param subtypeVarAssigns a map with type variables
+ * @return the {@link Map} with type arguments
+ */
+ private static Map<TypeVariable<?>, Type> getTypeArguments(
+ final ParameterizedType parameterizedType, final Class<?> toClass,
+ final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
+ final Class<?> cls = getRawType(parameterizedType);
+
+ // make sure they're assignable
+ if (!isAssignable(cls, toClass)) {
+ return null;
+ }
+
+ final Type ownerType = parameterizedType.getOwnerType();
+ final Map<TypeVariable<?>, Type> typeVarAssigns;
+
+ if (ownerType instanceof ParameterizedType) {
+ // get the owner type arguments first
+ final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType;
+ typeVarAssigns = getTypeArguments(parameterizedOwnerType,
+ getRawType(parameterizedOwnerType), subtypeVarAssigns);
+ } else {
+ // no owner, prep the type variable assignments map
+ typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>()
+ : new HashMap<>(subtypeVarAssigns);
+ }
+
+ // get the subject parameterized type's arguments
+ final Type[] typeArgs = parameterizedType.getActualTypeArguments();
+ // and get the corresponding type variables from the raw class
+ final TypeVariable<?>[] typeParams = cls.getTypeParameters();
+
+ // map the arguments to their respective type variables
+ for (int i = 0; i < typeParams.length; i++) {
+ final Type typeArg = typeArgs[i];
+ typeVarAssigns.put(
+ typeParams[i],
+ typeVarAssigns.getOrDefault(typeArg, typeArg)
+ );
+ }
+
+ if (toClass.equals(cls)) {
+ // target class has been reached. Done.
+ return typeVarAssigns;
+ }
+
+ // walk the inheritance hierarchy until the target class is reached
+ return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns);
+ }
+
+ /**
+ * Gets the type arguments of a class/interface based on a subtype. For
+ * instance, this method will determine that both of the parameters for the
+ * interface {@link Map} are {@link Object} for the subtype
+ * {@link java.util.Properties Properties} even though the subtype does not
+ * directly implement the {@link Map} interface.
+ *
+ * <p>
+ * This method returns {@code null} if {@code type} is not assignable to
+ * {@code toClass}. It returns an empty map if none of the classes or
+ * interfaces in its inheritance hierarchy specify any type arguments.
+ * </p>
+ *
+ * <p>
+ * A side effect of this method is that it also retrieves the type
+ * arguments for the classes and interfaces that are part of the hierarchy
+ * between {@code type} and {@code toClass}. So with the above
+ * example, this method will also determine that the type arguments for
+ * {@link java.util.Hashtable Hashtable} are also both {@link Object}.
+ * In cases where the interface specified by {@code toClass} is
+ * (indirectly) implemented more than once (e.g. where {@code toClass}
+ * specifies the interface {@link java.lang.Iterable Iterable} and
+ * {@code type} specifies a parameterized type that implements both
+ * {@link java.util.Set Set} and {@link java.util.Collection Collection}),
+ * this method will look at the inheritance hierarchy of only one of the
+ * implementations/subclasses; the first interface encountered that isn't a
+ * subinterface to one of the others in the {@code type} to
+ * {@code toClass} hierarchy.
+ * </p>
+ *
+ * @param type the type from which to determine the type parameters of
+ * {@code toClass}
+ * @param toClass the class whose type parameters are to be determined based
+ * on the subtype {@code type}
+ * @return a {@link Map} of the type assignments for the type variables in
+ * each type in the inheritance hierarchy from {@code type} to
+ * {@code toClass} inclusive.
+ */
+ public static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, final Class<?> toClass) {
+ return getTypeArguments(type, toClass, null);
+ }
+
+ /**
+ * Gets a map of the type arguments of {@code type} in the context of {@code toClass}.
+ *
+ * @param type the type in question
+ * @param toClass the class
+ * @param subtypeVarAssigns a map with type variables
+ * @return the {@link Map} with type arguments
+ */
+ private static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, final Class<?> toClass,
+ final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
+ if (type instanceof Class<?>) {
+ return getTypeArguments((Class<?>) type, toClass, subtypeVarAssigns);
+ }
+
+ if (type instanceof ParameterizedType) {
+ return getTypeArguments((ParameterizedType) type, toClass, subtypeVarAssigns);
+ }
+
+ if (type instanceof GenericArrayType) {
+ return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass
+ .isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns);
+ }
+
+ // since wildcard types are not assignable to classes, should this just
+ // return null?
+ if (type instanceof WildcardType) {
+ for (final Type bound : getImplicitUpperBounds((WildcardType) type)) {
+ // find the first bound that is assignable to the target class
+ if (isAssignable(bound, toClass)) {
+ return getTypeArguments(bound, toClass, subtypeVarAssigns);
+ }
+ }
+
+ return null;
+ }
+
+ if (type instanceof TypeVariable<?>) {
+ for (final Type bound : getImplicitBounds((TypeVariable<?>) type)) {
+ // find the first bound that is assignable to the target class
+ if (isAssignable(bound, toClass)) {
+ return getTypeArguments(bound, toClass, subtypeVarAssigns);
+ }
+ }
+
+ return null;
+ }
+ throw new IllegalStateException("found an unhandled type: " + type);
+ }
+
+ /**
+ * Tests whether the specified type denotes an array type.
+ *
+ * @param type the type to be checked
+ * @return {@code true} if {@code type} is an array class or a {@link GenericArrayType}.
+ */
+ public static boolean isArrayType(final Type type) {
+ return type instanceof GenericArrayType || type instanceof Class<?> && ((Class<?>) type).isArray();
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target class
+ * following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toClass the target class
+ * @return {@code true} if {@code type} is assignable to {@code toClass}.
+ */
+ private static boolean isAssignable(final Type type, final Class<?> toClass) {
+ if (type == null) {
+ // consistency with ClassUtils.isAssignable() behavior
+ return toClass == null || !toClass.isPrimitive();
+ }
+
+ // only a null type can be assigned to null type which
+ // would have cause the previous to return true
+ if (toClass == null) {
+ return false;
+ }
+
+ // all types are assignable to themselves
+ if (toClass.equals(type)) {
+ return true;
+ }
+
+ if (type instanceof Class<?>) {
+ // just comparing two classes
+ return ClassUtils.isAssignable((Class<?>) type, toClass);
+ }
+
+ if (type instanceof ParameterizedType) {
+ // only have to compare the raw type to the class
+ return isAssignable(getRawType((ParameterizedType) type), toClass);
+ }
+
+ // *
+ if (type instanceof TypeVariable<?>) {
+ // if any of the bounds are assignable to the class, then the
+ // type is assignable to the class.
+ for (final Type bound : ((TypeVariable<?>) type).getBounds()) {
+ if (isAssignable(bound, toClass)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // the only classes to which a generic array type can be assigned
+ // are class Object and array classes
+ if (type instanceof GenericArrayType) {
+ return toClass.equals(Object.class)
+ || toClass.isArray()
+ && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass
+ .getComponentType());
+ }
+
+ // wildcard types are not assignable to a class (though one would think
+ // "? super Object" would be assignable to Object)
+ if (type instanceof WildcardType) {
+ return false;
+ }
+
+ throw new IllegalStateException("found an unhandled type: " + type);
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target
+ * generic array type following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toGenericArrayType the target generic array type
+ * @param typeVarAssigns a map with type variables
+ * @return {@code true} if {@code type} is assignable to
+ * {@code toGenericArrayType}.
+ */
+ private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (type == null) {
+ return true;
+ }
+
+ // only a null type can be assigned to null type which
+ // would have cause the previous to return true
+ if (toGenericArrayType == null) {
+ return false;
+ }
+
+ // all types are assignable to themselves
+ if (toGenericArrayType.equals(type)) {
+ return true;
+ }
+
+ final Type toComponentType = toGenericArrayType.getGenericComponentType();
+
+ if (type instanceof Class<?>) {
+ final Class<?> cls = (Class<?>) type;
+
+ // compare the component types
+ return cls.isArray()
+ && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns);
+ }
+
+ if (type instanceof GenericArrayType) {
+ // compare the component types
+ return isAssignable(((GenericArrayType) type).getGenericComponentType(),
+ toComponentType, typeVarAssigns);
+ }
+
+ if (type instanceof WildcardType) {
+ // so long as one of the upper bounds is assignable, it's good
+ for (final Type bound : getImplicitUpperBounds((WildcardType) type)) {
+ if (isAssignable(bound, toGenericArrayType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (type instanceof TypeVariable<?>) {
+ // probably should remove the following logic and just return false.
+ // type variables cannot specify arrays as bounds.
+ for (final Type bound : getImplicitBounds((TypeVariable<?>) type)) {
+ if (isAssignable(bound, toGenericArrayType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (type instanceof ParameterizedType) {
+ // the raw type of a parameterized type is never an array or
+ // generic array, otherwise the declaration would look like this:
+ // Collection[]< ? extends String > collection;
+ return false;
+ }
+
+ throw new IllegalStateException("found an unhandled type: " + type);
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target
+ * parameterized type following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toParameterizedType the target parameterized type
+ * @param typeVarAssigns a map with type variables
+ * @return {@code true} if {@code type} is assignable to {@code toType}.
+ */
+ private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (type == null) {
+ return true;
+ }
+
+ // only a null type can be assigned to null type which
+ // would have cause the previous to return true
+ if (toParameterizedType == null) {
+ return false;
+ }
+
+ // cannot cast an array type to a parameterized type.
+ if (type instanceof GenericArrayType) {
+ return false;
+ }
+
+ // all types are assignable to themselves
+ if (toParameterizedType.equals(type)) {
+ return true;
+ }
+
+ // get the target type's raw type
+ final Class<?> toClass = getRawType(toParameterizedType);
+ // get the subject type's type arguments including owner type arguments
+ // and supertype arguments up to and including the target class.
+ final Map<TypeVariable<?>, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null);
+
+ // null means the two types are not compatible
+ if (fromTypeVarAssigns == null) {
+ return false;
+ }
+
+ // compatible types, but there's no type arguments. this is equivalent
+ // to comparing Map< ?, ? > to Map, and raw types are always assignable
+ // to parameterized types.
+ if (fromTypeVarAssigns.isEmpty()) {
+ return true;
+ }
+
+ // get the target type's type arguments including owner type arguments
+ final Map<TypeVariable<?>, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType,
+ toClass, typeVarAssigns);
+
+ // now to check each type argument
+ for (final TypeVariable<?> var : toTypeVarAssigns.keySet()) {
+ final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns);
+ final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns);
+
+ if (toTypeArg == null && fromTypeArg instanceof Class) {
+ continue;
+ }
+
+ // parameters must either be absent from the subject type, within
+ // the bounds of the wildcard type, or be an exact match to the
+ // parameters of the target type.
+ if (fromTypeArg != null && toTypeArg != null
+ && !toTypeArg.equals(fromTypeArg)
+ && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg,
+ typeVarAssigns))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target type
+ * following the Java generics rules. If both types are {@link Class}
+ * objects, the method returns the result of
+ * {@link ClassUtils#isAssignable(Class, Class)}.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toType the target type
+ * @return {@code true} if {@code type} is assignable to {@code toType}.
+ */
+ public static boolean isAssignable(final Type type, final Type toType) {
+ return isAssignable(type, toType, null);
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target type
+ * following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toType the target type
+ * @param typeVarAssigns optional map of type variable assignments
+ * @return {@code true} if {@code type} is assignable to {@code toType}.
+ */
+ private static boolean isAssignable(final Type type, final Type toType,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (toType == null || toType instanceof Class<?>) {
+ return isAssignable(type, (Class<?>) toType);
+ }
+
+ if (toType instanceof ParameterizedType) {
+ return isAssignable(type, (ParameterizedType) toType, typeVarAssigns);
+ }
+
+ if (toType instanceof GenericArrayType) {
+ return isAssignable(type, (GenericArrayType) toType, typeVarAssigns);
+ }
+
+ if (toType instanceof WildcardType) {
+ return isAssignable(type, (WildcardType) toType, typeVarAssigns);
+ }
+
+ if (toType instanceof TypeVariable<?>) {
+ return isAssignable(type, (TypeVariable<?>) toType, typeVarAssigns);
+ }
+
+ throw new IllegalStateException("found an unhandled type: " + toType);
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target type
+ * variable following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toTypeVariable the target type variable
+ * @param typeVarAssigns a map with type variables
+ * @return {@code true} if {@code type} is assignable to
+ * {@code toTypeVariable}.
+ */
+ private static boolean isAssignable(final Type type, final TypeVariable<?> toTypeVariable,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (type == null) {
+ return true;
+ }
+
+ // only a null type can be assigned to null type which
+ // would have cause the previous to return true
+ if (toTypeVariable == null) {
+ return false;
+ }
+
+ // all types are assignable to themselves
+ if (toTypeVariable.equals(type)) {
+ return true;
+ }
+
+ if (type instanceof TypeVariable<?>) {
+ // a type variable is assignable to another type variable, if
+ // and only if the former is the latter, extends the latter, or
+ // is otherwise a descendant of the latter.
+ final Type[] bounds = getImplicitBounds((TypeVariable<?>) type);
+
+ for (final Type bound : bounds) {
+ if (isAssignable(bound, toTypeVariable, typeVarAssigns)) {
+ return true;
+ }
+ }
+ }
+
+ if (type instanceof Class<?> || type instanceof ParameterizedType
+ || type instanceof GenericArrayType || type instanceof WildcardType) {
+ return false;
+ }
+
+ throw new IllegalStateException("found an unhandled type: " + type);
+ }
+
+ /**
+ * Tests if the subject type may be implicitly cast to the target
+ * wildcard type following the Java generics rules.
+ *
+ * @param type the subject type to be assigned to the target type
+ * @param toWildcardType the target wildcard type
+ * @param typeVarAssigns a map with type variables
+ * @return {@code true} if {@code type} is assignable to
+ * {@code toWildcardType}.
+ */
+ private static boolean isAssignable(final Type type, final WildcardType toWildcardType,
+ final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (type == null) {
+ return true;
+ }
+
+ // only a null type can be assigned to null type which
+ // would have cause the previous to return true
+ if (toWildcardType == null) {
+ return false;
+ }
+
+ // all types are assignable to themselves
+ if (toWildcardType.equals(type)) {
+ return true;
+ }
+
+ final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType);
+ final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType);
+
+ if (type instanceof WildcardType) {
+ final WildcardType wildcardType = (WildcardType) type;
+ final Type[] upperBounds = getImplicitUpperBounds(wildcardType);
+ final Type[] lowerBounds = getImplicitLowerBounds(wildcardType);
+
+ for (Type toBound : toUpperBounds) {
+ // if there are assignments for unresolved type variables,
+ // now's the time to substitute them.
+ toBound = substituteTypeVariables(toBound, typeVarAssigns);
+
+ // each upper bound of the subject type has to be assignable to
+ // each
+ // upper bound of the target type
+ for (final Type bound : upperBounds) {
+ if (!isAssignable(bound, toBound, typeVarAssigns)) {
+ return false;
+ }
+ }
+ }
+
+ for (Type toBound : toLowerBounds) {
+ // if there are assignments for unresolved type variables,
+ // now's the time to substitute them.
+ toBound = substituteTypeVariables(toBound, typeVarAssigns);
+
+ // each lower bound of the target type has to be assignable to
+ // each
+ // lower bound of the subject type
+ for (final Type bound : lowerBounds) {
+ if (!isAssignable(toBound, bound, typeVarAssigns)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ for (final Type toBound : toUpperBounds) {
+ // if there are assignments for unresolved type variables,
+ // now's the time to substitute them.
+ if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns),
+ typeVarAssigns)) {
+ return false;
+ }
+ }
+
+ for (final Type toBound : toLowerBounds) {
+ // if there are assignments for unresolved type variables,
+ // now's the time to substitute them.
+ if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type,
+ typeVarAssigns)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests if the given value can be assigned to the target type
+ * following the Java generics rules.
+ *
+ * @param value the value to be checked
+ * @param type the target type
+ * @return {@code true} if {@code value} is an instance of {@code type}.
+ */
+ public static boolean isInstance(final Object value, final Type type) {
+ if (type == null) {
+ return false;
+ }
+
+ return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive()
+ : isAssignable(value.getClass(), type, null);
+ }
+
+ /**
+ * Maps type variables.
+ *
+ * @param <T> the generic type of the class in question
+ * @param cls the class in question
+ * @param parameterizedType the parameterized type
+ * @param typeVarAssigns the map to be filled
+ */
+ private static <T> void mapTypeVariablesToArguments(final Class<T> cls,
+ final ParameterizedType parameterizedType, final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ // capture the type variables from the owner type that have assignments
+ final Type ownerType = parameterizedType.getOwnerType();
+
+ if (ownerType instanceof ParameterizedType) {
+ // recursion to make sure the owner's owner type gets processed
+ mapTypeVariablesToArguments(cls, (ParameterizedType) ownerType, typeVarAssigns);
+ }
+
+ // parameterizedType is a generic interface/class (or it's in the owner
+ // hierarchy of said interface/class) implemented/extended by the class
+ // cls. Find out which type variables of cls are type arguments of
+ // parameterizedType:
+ final Type[] typeArgs = parameterizedType.getActualTypeArguments();
+
+ // of the cls's type variables that are arguments of parameterizedType,
+ // find out which ones can be determined from the super type's arguments
+ final TypeVariable<?>[] typeVars = getRawType(parameterizedType).getTypeParameters();
+
+ // use List view of type parameters of cls so the contains() method can be used:
+ final List<TypeVariable<Class<T>>> typeVarList = Arrays.asList(cls
+ .getTypeParameters());
+
+ for (int i = 0; i < typeArgs.length; i++) {
+ final TypeVariable<?> typeVar = typeVars[i];
+ final Type typeArg = typeArgs[i];
+
+ // argument of parameterizedType is a type variable of cls
+ if (typeVarList.contains(typeArg)
+ // type variable of parameterizedType has an assignment in
+ // the super type.
+ && typeVarAssigns.containsKey(typeVar)) {
+ // map the assignment to the cls's type variable
+ typeVarAssigns.put((TypeVariable<?>) typeArg, typeVarAssigns.get(typeVar));
+ }
+ }
+ }
+
+ /**
+ * Strips out the redundant upper bound types in type
+ * variable types and wildcard types (or it would with wildcard types if
+ * multiple upper bounds were allowed).
+ *
+ * <p>
+ * Example, with the variable type declaration:
+ * </p>
+ *
+ * <pre>&lt;K extends java.util.Collection&lt;String&gt; &amp;
+ * java.util.List&lt;String&gt;&gt;</pre>
+ *
+ * <p>
+ * since {@link List} is a subinterface of {@link Collection},
+ * this method will return the bounds as if the declaration had been:
+ * </p>
+ *
+ * <pre>&lt;K extends java.util.List&lt;String&gt;&gt;</pre>
+ *
+ * @param bounds an array of types representing the upper bounds of either
+ * {@link WildcardType} or {@link TypeVariable}, not {@code null}.
+ * @return an array containing the values from {@code bounds} minus the
+ * redundant types.
+ */
+ public static Type[] normalizeUpperBounds(final Type[] bounds) {
+ Objects.requireNonNull(bounds, "bounds");
+ // don't bother if there's only one (or none) type
+ if (bounds.length < 2) {
+ return bounds;
+ }
+
+ final Set<Type> types = new HashSet<>(bounds.length);
+
+ for (final Type type1 : bounds) {
+ boolean subtypeFound = false;
+
+ for (final Type type2 : bounds) {
+ if (type1 != type2 && isAssignable(type2, type1, null)) {
+ subtypeFound = true;
+ break;
+ }
+ }
+
+ if (!subtypeFound) {
+ types.add(type1);
+ }
+ }
+
+ return types.toArray(ArrayUtils.EMPTY_TYPE_ARRAY);
+ }
+
+ /**
+ * Creates a parameterized type instance.
+ *
+ * @param rawClass the raw class to create a parameterized type instance for
+ * @param typeVariableMap the map used for parameterization
+ * @return {@link ParameterizedType}
+ * @throws NullPointerException if either {@code rawClass} or {@code typeVariableMap} is {@code null}
+ * @since 3.2
+ */
+ public static final ParameterizedType parameterize(final Class<?> rawClass,
+ final Map<TypeVariable<?>, Type> typeVariableMap) {
+ Objects.requireNonNull(rawClass, "rawClass");
+ Objects.requireNonNull(typeVariableMap, "typeVariableMap");
+ return parameterizeWithOwner(null, rawClass,
+ extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters()));
+ }
+
+ /**
+ * Creates a parameterized type instance.
+ *
+ * @param rawClass the raw class to create a parameterized type instance for
+ * @param typeArguments the types used for parameterization
+ * @return {@link ParameterizedType}
+ * @throws NullPointerException if {@code rawClass} is {@code null}
+ * @since 3.2
+ */
+ public static final ParameterizedType parameterize(final Class<?> rawClass, final Type... typeArguments) {
+ return parameterizeWithOwner(null, rawClass, typeArguments);
+ }
+
+ /**
+ * Formats a {@link ParameterizedType} as a {@link String}.
+ *
+ * @param parameterizedType {@link ParameterizedType} to format
+ * @return String
+ * @since 3.2
+ */
+ private static String parameterizedTypeToString(final ParameterizedType parameterizedType) {
+ final StringBuilder builder = new StringBuilder();
+
+ final Type useOwner = parameterizedType.getOwnerType();
+ final Class<?> raw = (Class<?>) parameterizedType.getRawType();
+
+ if (useOwner == null) {
+ builder.append(raw.getName());
+ } else {
+ if (useOwner instanceof Class<?>) {
+ builder.append(((Class<?>) useOwner).getName());
+ } else {
+ builder.append(useOwner.toString());
+ }
+ builder.append('.').append(raw.getSimpleName());
+ }
+
+ final int[] recursiveTypeIndexes = findRecursiveTypes(parameterizedType);
+
+ if (recursiveTypeIndexes.length > 0) {
+ appendRecursiveTypes(builder, recursiveTypeIndexes, parameterizedType.getActualTypeArguments());
+ } else {
+ appendAllTo(builder.append('<'), ", ", parameterizedType.getActualTypeArguments()).append('>');
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Creates a parameterized type instance.
+ *
+ * @param owner the owning type
+ * @param rawClass the raw class to create a parameterized type instance for
+ * @param typeVariableMap the map used for parameterization
+ * @return {@link ParameterizedType}
+ * @throws NullPointerException if either {@code rawClass} or {@code typeVariableMap}
+ * is {@code null}
+ * @since 3.2
+ */
+ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass,
+ final Map<TypeVariable<?>, Type> typeVariableMap) {
+ Objects.requireNonNull(rawClass, "rawClass");
+ Objects.requireNonNull(typeVariableMap, "typeVariableMap");
+ return parameterizeWithOwner(owner, rawClass,
+ extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters()));
+ }
+
+ /**
+ * Creates a parameterized type instance.
+ *
+ * @param owner the owning type
+ * @param rawClass the raw class to create a parameterized type instance for
+ * @param typeArguments the types used for parameterization
+ *
+ * @return {@link ParameterizedType}
+ * @throws NullPointerException if {@code rawClass} is {@code null}
+ * @since 3.2
+ */
+ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass,
+ final Type... typeArguments) {
+ Objects.requireNonNull(rawClass, "rawClass");
+ final Type useOwner;
+ if (rawClass.getEnclosingClass() == null) {
+ Validate.isTrue(owner == null, "no owner allowed for top-level %s", rawClass);
+ useOwner = null;
+ } else if (owner == null) {
+ useOwner = rawClass.getEnclosingClass();
+ } else {
+ Validate.isTrue(isAssignable(owner, rawClass.getEnclosingClass()),
+ "%s is invalid owner type for parameterized %s", owner, rawClass);
+ useOwner = owner;
+ }
+ Validate.noNullElements(typeArguments, "null type argument at index %s");
+ Validate.isTrue(rawClass.getTypeParameters().length == typeArguments.length,
+ "invalid number of type parameters specified: expected %d, got %d", rawClass.getTypeParameters().length,
+ typeArguments.length);
+
+ return new ParameterizedTypeImpl(rawClass, useOwner, typeArguments);
+ }
+
+ /**
+ * Finds the mapping for {@code type} in {@code typeVarAssigns}.
+ *
+ * @param type the type to be replaced
+ * @param typeVarAssigns the map with type variables
+ * @return the replaced type
+ * @throws IllegalArgumentException if the type cannot be substituted
+ */
+ private static Type substituteTypeVariables(final Type type, final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ if (type instanceof TypeVariable<?> && typeVarAssigns != null) {
+ final Type replacementType = typeVarAssigns.get(type);
+
+ if (replacementType == null) {
+ throw new IllegalArgumentException("missing assignment type for type variable "
+ + type);
+ }
+ return replacementType;
+ }
+ return type;
+ }
+
+ /**
+ * Formats a {@link TypeVariable} including its {@link GenericDeclaration}.
+ *
+ * @param typeVariable the type variable to create a String representation for, not {@code null}
+ * @return String
+ * @since 3.2
+ */
+ public static String toLongString(final TypeVariable<?> typeVariable) {
+ Objects.requireNonNull(typeVariable, "typeVariable");
+ final StringBuilder buf = new StringBuilder();
+ final GenericDeclaration d = typeVariable.getGenericDeclaration();
+ if (d instanceof Class<?>) {
+ Class<?> c = (Class<?>) d;
+ while (true) {
+ if (c.getEnclosingClass() == null) {
+ buf.insert(0, c.getName());
+ break;
+ }
+ buf.insert(0, c.getSimpleName()).insert(0, '.');
+ c = c.getEnclosingClass();
+ }
+ } else if (d instanceof Type) {// not possible as of now
+ buf.append(toString((Type) d));
+ } else {
+ buf.append(d);
+ }
+ return buf.append(':').append(typeVariableToString(typeVariable)).toString();
+ }
+
+ private static <T> String toString(final T object) {
+ return object instanceof Type ? toString((Type) object) : object.toString();
+ }
+
+ /**
+ * Formats a given type as a Java-esque String.
+ *
+ * @param type the type to create a String representation for, not {@code null}
+ * @return String
+ * @since 3.2
+ */
+ public static String toString(final Type type) {
+ Objects.requireNonNull(type, "type");
+ if (type instanceof Class<?>) {
+ return classToString((Class<?>) type);
+ }
+ if (type instanceof ParameterizedType) {
+ return parameterizedTypeToString((ParameterizedType) type);
+ }
+ if (type instanceof WildcardType) {
+ return wildcardTypeToString((WildcardType) type);
+ }
+ if (type instanceof TypeVariable<?>) {
+ return typeVariableToString((TypeVariable<?>) type);
+ }
+ if (type instanceof GenericArrayType) {
+ return genericArrayTypeToString((GenericArrayType) type);
+ }
+ throw new IllegalArgumentException(ObjectUtils.identityToString(type));
+ }
+
+ /**
+ * Determines whether or not specified types satisfy the bounds of their
+ * mapped type variables. When a type parameter extends another (such as
+ * {@code <T, S extends T>}), uses another as a type parameter (such as
+ * {@code <T, S extends Comparable>>}), or otherwise depends on
+ * another type variable to be specified, the dependencies must be included
+ * in {@code typeVarAssigns}.
+ *
+ * @param typeVariableMap specifies the potential types to be assigned to the
+ * type variables, not {@code null}.
+ * @return whether or not the types can be assigned to their respective type
+ * variables.
+ */
+ public static boolean typesSatisfyVariables(final Map<TypeVariable<?>, Type> typeVariableMap) {
+ Objects.requireNonNull(typeVariableMap, "typeVariableMap");
+ // all types must be assignable to all the bounds of their mapped
+ // type variable.
+ for (final Map.Entry<TypeVariable<?>, Type> entry : typeVariableMap.entrySet()) {
+ final TypeVariable<?> typeVar = entry.getKey();
+ final Type type = entry.getValue();
+
+ for (final Type bound : getImplicitBounds(typeVar)) {
+ if (!isAssignable(type, substituteTypeVariables(bound, typeVariableMap),
+ typeVariableMap)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Formats a {@link TypeVariable} as a {@link String}.
+ *
+ * @param typeVariable {@link TypeVariable} to format
+ * @return String
+ * @since 3.2
+ */
+ private static String typeVariableToString(final TypeVariable<?> typeVariable) {
+ final StringBuilder buf = new StringBuilder(typeVariable.getName());
+ final Type[] bounds = typeVariable.getBounds();
+ if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals(bounds[0]))) {
+ buf.append(" extends ");
+ appendAllTo(buf, " & ", typeVariable.getBounds());
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Unrolls variables in a type bounds array.
+ *
+ * @param typeArguments assignments {@link Map}
+ * @param bounds in which to expand variables
+ * @return {@code bounds} with any variables reassigned
+ * @since 3.2
+ */
+ private static Type[] unrollBounds(final Map<TypeVariable<?>, Type> typeArguments, final Type[] bounds) {
+ Type[] result = bounds;
+ int i = 0;
+ for (; i < result.length; i++) {
+ final Type unrolled = unrollVariables(typeArguments, result[i]);
+ if (unrolled == null) {
+ result = ArrayUtils.remove(result, i--);
+ } else {
+ result[i] = unrolled;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Looks up {@code typeVariable} in {@code typeVarAssigns} <em>transitively</em>, i.e. keep looking until the value
+ * found is <em>not</em> a type variable.
+ *
+ * @param typeVariable the type variable to look up
+ * @param typeVarAssigns the map used for the look-up
+ * @return Type or {@code null} if some variable was not in the map
+ * @since 3.2
+ */
+ private static Type unrollVariableAssignments(TypeVariable<?> typeVariable, final Map<TypeVariable<?>, Type> typeVarAssigns) {
+ Type result;
+ do {
+ result = typeVarAssigns.get(typeVariable);
+ if (!(result instanceof TypeVariable<?>) || result.equals(typeVariable)) {
+ break;
+ }
+ typeVariable = (TypeVariable<?>) result;
+ } while (true);
+ return result;
+ }
+
+ /**
+ * Gets a type representing {@code type} with variable assignments "unrolled."
+ *
+ * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)}
+ * @param type the type to unroll variable assignments for
+ * @return Type
+ * @since 3.2
+ */
+ public static Type unrollVariables(Map<TypeVariable<?>, Type> typeArguments, final Type type) {
+ if (typeArguments == null) {
+ typeArguments = Collections.emptyMap();
+ }
+ if (containsTypeVariables(type)) {
+ if (type instanceof TypeVariable<?>) {
+ return unrollVariables(typeArguments, typeArguments.get(type));
+ }
+ if (type instanceof ParameterizedType) {
+ final ParameterizedType p = (ParameterizedType) type;
+ final Map<TypeVariable<?>, Type> parameterizedTypeArguments;
+ if (p.getOwnerType() == null) {
+ parameterizedTypeArguments = typeArguments;
+ } else {
+ parameterizedTypeArguments = new HashMap<>(typeArguments);
+ parameterizedTypeArguments.putAll(getTypeArguments(p));
+ }
+ final Type[] args = p.getActualTypeArguments();
+ for (int i = 0; i < args.length; i++) {
+ final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i]);
+ if (unrolled != null) {
+ args[i] = unrolled;
+ }
+ }
+ return parameterizeWithOwner(p.getOwnerType(), (Class<?>) p.getRawType(), args);
+ }
+ if (type instanceof WildcardType) {
+ final WildcardType wild = (WildcardType) type;
+ return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild.getUpperBounds()))
+ .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build();
+ }
+ }
+ return type;
+ }
+
+ /**
+ * Gets a {@link WildcardTypeBuilder}.
+ *
+ * @return {@link WildcardTypeBuilder}
+ * @since 3.2
+ */
+ public static WildcardTypeBuilder wildcardType() {
+ return new WildcardTypeBuilder();
+ }
+
+ /**
+ * Formats a {@link WildcardType} as a {@link String}.
+ *
+ * @param wildcardType {@link WildcardType} to format
+ * @return String
+ * @since 3.2
+ */
+ private static String wildcardTypeToString(final WildcardType wildcardType) {
+ final StringBuilder buf = new StringBuilder().append('?');
+ final Type[] lowerBounds = wildcardType.getLowerBounds();
+ final Type[] upperBounds = wildcardType.getUpperBounds();
+ if (lowerBounds.length > 1 || lowerBounds.length == 1 && lowerBounds[0] != null) {
+ appendAllTo(buf.append(" super "), " & ", lowerBounds);
+ } else if (upperBounds.length > 1 || upperBounds.length == 1 && !Object.class.equals(upperBounds[0])) {
+ appendAllTo(buf.append(" extends "), " & ", upperBounds);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Wraps the specified {@link Class} in a {@link Typed} wrapper.
+ *
+ * @param <T> generic type
+ * @param type to wrap
+ * @return Typed&lt;T&gt;
+ * @since 3.2
+ */
+ public static <T> Typed<T> wrap(final Class<T> type) {
+ return wrap((Type) type);
+ }
+
+ /**
+ * Wraps the specified {@link Type} in a {@link Typed} wrapper.
+ *
+ * @param <T> inferred generic type
+ * @param type to wrap
+ * @return Typed&lt;T&gt;
+ * @since 3.2
+ */
+ public static <T> Typed<T> wrap(final Type type) {
+ return () -> type;
+ }
+
+ /**
+ * {@link TypeUtils} instances should NOT be constructed in standard
+ * programming. Instead, the class should be used as
+ * {@code TypeUtils.isAssignable(cls, toClass)}.
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean instance
+ * to operate.
+ * </p>
+ */
+ public TypeUtils() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/Typed.java b/src/main/java/org/apache/commons/lang3/reflect/Typed.java
new file mode 100644
index 000000000..f8432d521
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/Typed.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.Type;
+
+/**
+ * Generalization of "has a type."
+ *
+ * @param <T> the type
+ * @see TypeLiteral
+ * @since 3.2
+ */
+@FunctionalInterface
+public interface Typed<T> {
+
+ /**
+ * Gets the {@link Type} represented by this entity.
+ *
+ * @return Type
+ */
+ Type getType();
+}
diff --git a/src/main/java/org/apache/commons/lang3/reflect/package-info.java b/src/main/java/org/apache/commons/lang3/reflect/package-info.java
new file mode 100644
index 000000000..9e242883a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/reflect/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Accumulates common high-level uses of the {@code java.lang.reflect} APIs.
+ * <p>These classes are immutable, and therefore thread-safe.</p>
+ *
+ * @since 3.0
+ */
+package org.apache.commons.lang3.reflect;
diff --git a/src/main/java/org/apache/commons/lang3/stream/IntStreams.java b/src/main/java/org/apache/commons/lang3/stream/IntStreams.java
new file mode 100644
index 000000000..3e162afa9
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/stream/IntStreams.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.stream;
+
+import java.util.stream.IntStream;
+
+/**
+ * Factory for {@link IntStream}.
+ * <p>
+ * <small> Only a factory for now but could hold other functionality.</small>
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public class IntStreams {
+
+ /**
+ * Shorthand for {@code IntStream.range(0, i)}.
+ *
+ * @param endExclusive the exclusive upper bound.
+ * @return a sequential {@link IntStream} for the range of {@code int} elements.
+ */
+ public static IntStream range(final int endExclusive) {
+ return IntStream.range(0, endExclusive);
+ }
+
+ /**
+ * Shorthand for {@code IntStream.rangeClosed(0, i)}.
+ *
+ * @param endInclusive the inclusive upper bound.
+ * @return a sequential {@link IntStream} for the range of {@code int} elements.
+ */
+ public static IntStream rangeClosed(final int endInclusive) {
+ return IntStream.rangeClosed(0, endInclusive);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/stream/LangCollectors.java b/src/main/java/org/apache/commons/lang3/stream/LangCollectors.java
new file mode 100644
index 000000000..abd3b10f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/stream/LangCollectors.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.stream;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Implementations of {@link Collector} that implement various useful reduction operations.
+ * <p>
+ * This class is called {@code LangCollectors} instead of {@code Collectors} to avoid clashes with {@link Collectors}.
+ * </p>
+ *
+ * @since 3.13.0
+ */
+public final class LangCollectors {
+
+ /**
+ * Simple implementation class for {@code Collector}.
+ *
+ * @param <T> the type of elements to be collected
+ * @param <R> the type of the result
+ */
+ private static class SimpleCollector<T, A, R> implements Collector<T, A, R> {
+
+ private final BiConsumer<A, T> accumulator;
+ private final Set<Characteristics> characteristics;
+ private final BinaryOperator<A> combiner;
+ private final Function<A, R> finisher;
+ private final Supplier<A> supplier;
+
+ private SimpleCollector(final Supplier<A> supplier, final BiConsumer<A, T> accumulator, final BinaryOperator<A> combiner, final Function<A, R> finisher,
+ final Set<Characteristics> characteristics) {
+ this.supplier = supplier;
+ this.accumulator = accumulator;
+ this.combiner = combiner;
+ this.finisher = finisher;
+ this.characteristics = characteristics;
+ }
+
+ @Override
+ public BiConsumer<A, T> accumulator() {
+ return accumulator;
+ }
+
+ @Override
+ public Set<Characteristics> characteristics() {
+ return characteristics;
+ }
+
+ @Override
+ public BinaryOperator<A> combiner() {
+ return combiner;
+ }
+
+ @Override
+ public Function<A, R> finisher() {
+ return finisher;
+ }
+
+ @Override
+ public Supplier<A> supplier() {
+ return supplier;
+ }
+ }
+
+ private static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();
+
+ /**
+ * Returns a {@code Collector} that concatenates the input elements, separated by the specified delimiter, in encounter
+ * order.
+ * <p>
+ * This is a variation of {@link Collectors#joining()} that works with any element class, not just {@code CharSequence}.
+ * </p>
+ *
+ * @return A {@code Collector} which concatenates Object elements, separated by the specified delimiter, in encounter
+ * order.
+ */
+ public static Collector<Object, ?, String> joining() {
+ return new SimpleCollector<>(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString, CH_NOID);
+ }
+
+ /**
+ * Returns a {@code Collector} that concatenates the input elements, separated by the specified delimiter, in encounter
+ * order.
+ * <p>
+ * This is a variation of {@link Collectors#joining(CharSequence)} that works with any element class, not just
+ * {@code CharSequence}.
+ * </p>
+ *
+ * @param delimiter the delimiter to be used between each element.
+ * @return A {@code Collector} which concatenates Object elements, separated by the specified delimiter, in encounter
+ * order.
+ */
+ public static Collector<Object, ?, String> joining(final CharSequence delimiter) {
+ return joining(delimiter, StringUtils.EMPTY, StringUtils.EMPTY);
+ }
+
+ /**
+ * Returns a {@code Collector} that concatenates the input elements, separated by the specified delimiter, with the
+ * specified prefix and suffix, in encounter order.
+ * <p>
+ * This is a variation of {@link Collectors#joining(CharSequence, CharSequence, CharSequence)} that works with any
+ * element class, not just {@code CharSequence}.
+ * </p>
+ *
+ * @param delimiter the delimiter to be used between each element
+ * @param prefix the sequence of characters to be used at the beginning of the joined result
+ * @param suffix the sequence of characters to be used at the end of the joined result
+ * @return A {@code Collector} which concatenates CharSequence elements, separated by the specified delimiter, in
+ * encounter order
+ */
+ public static Collector<Object, ?, String> joining(final CharSequence delimiter, final CharSequence prefix, final CharSequence suffix) {
+ return joining(delimiter, prefix, suffix, Objects::toString);
+ }
+
+ /**
+ * Returns a {@code Collector} that concatenates the input elements, separated by the specified delimiter, with the
+ * specified prefix and suffix, in encounter order.
+ * <p>
+ * This is a variation of {@link Collectors#joining(CharSequence, CharSequence, CharSequence)} that works with any
+ * element class, not just {@code CharSequence}.
+ * </p>
+ *
+ * @param delimiter the delimiter to be used between each element
+ * @param prefix the sequence of characters to be used at the beginning of the joined result
+ * @param suffix the sequence of characters to be used at the end of the joined result
+ * @param toString A function that takes an Object and returns a non-null String.
+ * @return A {@code Collector} which concatenates CharSequence elements, separated by the specified delimiter, in
+ * encounter order
+ */
+ public static Collector<Object, ?, String> joining(final CharSequence delimiter, final CharSequence prefix, final CharSequence suffix,
+ final Function<Object, String> toString) {
+ return new SimpleCollector<>(() -> new StringJoiner(delimiter, prefix, suffix), (a, t) -> a.add(toString.apply(t)), StringJoiner::merge,
+ StringJoiner::toString, CH_NOID);
+ }
+
+ private LangCollectors() {
+ // No instance
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/stream/Streams.java b/src/main/java/org/apache/commons/lang3/stream/Streams.java
new file mode 100644
index 000000000..bd5ab630c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/stream/Streams.java
@@ -0,0 +1,816 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.stream;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.Spliterators.AbstractSpliterator;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.function.Failable;
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableFunction;
+import org.apache.commons.lang3.function.FailablePredicate;
+
+/**
+ * Provides utility functions, and classes for working with the {@code java.util.stream} package, or more generally,
+ * with Java 8 lambdas. More specifically, it attempts to address the fact that lambdas are supposed not to throw
+ * Exceptions, at least not checked Exceptions, AKA instances of {@link Exception}. This enforces the use of constructs
+ * like:
+ *
+ * <pre>
+ * {@code
+ * Consumer<java.lang.reflect.Method> consumer = m -> {
+ * try {
+ * m.invoke(o, args);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * stream.forEach(consumer);
+ * }
+ * </pre>
+ * <p>
+ * Using a {@link FailableStream}, this can be rewritten as follows:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * Streams.failable(stream).forEach((m) -> m.invoke(o, args));
+ * }
+ * </pre>
+ *
+ * Obviously, the second version is much more concise and the spirit of Lambda expressions is met better than in the
+ * first version.
+ *
+ * @see Stream
+ * @see Failable
+ * @since 3.11
+ */
+public class Streams {
+
+ /**
+ * A Collector type for arrays.
+ *
+ * @param <E> The array type.
+ */
+ public static class ArrayCollector<E> implements Collector<E, List<E>, E[]> {
+ private static final Set<Characteristics> characteristics = Collections.emptySet();
+ private final Class<E> elementType;
+
+ /**
+ * Constructs a new instance for the given element type.
+ *
+ * @param elementType The element type.
+ */
+ public ArrayCollector(final Class<E> elementType) {
+ this.elementType = Objects.requireNonNull(elementType, "elementType");
+ }
+
+ @Override
+ public BiConsumer<List<E>, E> accumulator() {
+ return List::add;
+ }
+
+ @Override
+ public Set<Characteristics> characteristics() {
+ return characteristics;
+ }
+
+ @Override
+ public BinaryOperator<List<E>> combiner() {
+ return (left, right) -> {
+ left.addAll(right);
+ return left;
+ };
+ }
+
+ @Override
+ public Function<List<E>, E[]> finisher() {
+ return list -> list.toArray(ArrayUtils.newInstance(elementType, list.size()));
+ }
+
+ @Override
+ public Supplier<List<E>> supplier() {
+ return ArrayList::new;
+ }
+ }
+
+ /**
+ * Helps implement {@link Streams#of(Enumeration)}.
+ *
+ * @param <T> The element type.
+ */
+ private static class EnumerationSpliterator<T> extends AbstractSpliterator<T> {
+
+ private final Enumeration<T> enumeration;
+
+ /**
+ * Creates a spliterator reporting the given estimated size and additionalCharacteristics.
+ *
+ * @param estimatedSize the estimated size of this spliterator if known, otherwise {@code Long.MAX_VALUE}.
+ * @param additionalCharacteristics properties of this spliterator's source or elements. If {@code SIZED} is reported then this spliterator will
+ * additionally report {@code SUBSIZED}.
+ * @param enumeration The Enumeration to wrap.
+ */
+ protected EnumerationSpliterator(final long estimatedSize, final int additionalCharacteristics, final Enumeration<T> enumeration) {
+ super(estimatedSize, additionalCharacteristics);
+ this.enumeration = Objects.requireNonNull(enumeration, "enumeration");
+ }
+
+ @Override
+ public void forEachRemaining(final Consumer<? super T> action) {
+ while (enumeration.hasMoreElements()) {
+ next(action);
+ }
+ }
+
+ private boolean next(final Consumer<? super T> action) {
+ action.accept(enumeration.nextElement());
+ return true;
+
+ }
+
+ @Override
+ public boolean tryAdvance(final Consumer<? super T> action) {
+ return enumeration.hasMoreElements() && next(action);
+ }
+ }
+
+ /**
+ * A reduced, and simplified version of a {@link Stream} with failable method signatures.
+ *
+ * @param <T> The streams element type.
+ */
+ public static class FailableStream<T> {
+
+ private Stream<T> stream;
+ private boolean terminated;
+
+ /**
+ * Constructs a new instance with the given {@code stream}.
+ *
+ * @param stream The stream.
+ */
+ public FailableStream(final Stream<T> stream) {
+ 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.
+ *
+ * <p>
+ * This is a short-circuiting terminal operation.
+ * </p>
+ *
+ * Note This method evaluates the <em>universal quantification</em> 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 <em>vacuously satisfied</em> 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<T, ?> predicate) {
+ assertNotTerminated();
+ return stream().allMatch(Failable.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.
+ *
+ * <p>
+ * This is a short-circuiting terminal operation.
+ * </p>
+ *
+ * Note This method evaluates the <em>existential quantification</em> 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<T, ?> predicate) {
+ assertNotTerminated();
+ return stream().anyMatch(Failable.asPredicate(predicate));
+ }
+
+ /**
+ * Throws IllegalStateException if this stream is already terminated.
+ *
+ * @throws IllegalStateException if this stream is already terminated.
+ */
+ protected void assertNotTerminated() {
+ if (terminated) {
+ throw new IllegalStateException("This stream is already terminated.");
+ }
+ }
+
+ /**
+ * Performs a mutable reduction operation on the elements of this stream using a {@link Collector}. A {@link Collector}
+ * encapsulates the functions used as arguments to {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for
+ * reuse of collection strategies and composition of collect operations such as multiple-level grouping or partitioning.
+ *
+ * <p>
+ * If the underlying stream is parallel, and the {@link Collector} is concurrent, and either the stream is unordered or
+ * the collector is unordered, then a concurrent reduction will be performed (see {@link Collector} for details on
+ * concurrent reduction.)
+ * </p>
+ *
+ * <p>
+ * This is a terminal operation.
+ * </p>
+ *
+ * <p>
+ * When executed in parallel, multiple intermediate results may be instantiated, populated, and merged so as to maintain
+ * isolation of mutable data structures. Therefore, even when executed in parallel with non-thread-safe data structures
+ * (such as {@link ArrayList}), no additional synchronization is needed for a parallel reduction.
+ * </p>
+ *
+ * Note The following will accumulate strings into an ArrayList:
+ *
+ * <pre>
+ * {@code
+ * List<String> asList = stringStream.collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * <p>
+ * The following will classify {@code Person} objects by city:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
+ * }
+ * </pre>
+ *
+ * <p>
+ * The following will classify {@code Person} objects by state and city, cascading two {@link Collector}s together:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream
+ * .collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
+ * }
+ * </pre>
+ *
+ * @param <R> the type of the result
+ * @param <A> the intermediate accumulation type of the {@link Collector}
+ * @param collector the {@link Collector} describing the reduction
+ * @return the result of the reduction
+ * @see #collect(Supplier, BiConsumer, BiConsumer)
+ * @see Collectors
+ */
+ public <A, R> R collect(final Collector<? super T, A, R> collector) {
+ makeTerminated();
+ return stream().collect(collector);
+ }
+
+ /**
+ * Performs a mutable reduction operation on the elements of this FailableStream. A mutable reduction is one in which
+ * the reduced value is a mutable result container, such as an {@link ArrayList}, and elements are incorporated by
+ * updating the state of the result rather than by replacing the result. This produces a result equivalent to:
+ *
+ * <pre>
+ * {@code
+ * R result = supplier.get();
+ * for (T element : this stream)
+ * accumulator.accept(result, element);
+ * return result;
+ * }
+ * </pre>
+ *
+ * <p>
+ * Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations can be parallelized without requiring
+ * additional synchronization.
+ * </p>
+ *
+ * <p>
+ * This is a terminal operation.
+ * </p>
+ *
+ * Note There are many existing classes in the JDK whose signatures are well-suited for use with method references as
+ * arguments to {@code collect()}. For example, the following will accumulate strings into an {@link ArrayList}:
+ *
+ * <pre>
+ * {@code
+ * List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+ * }
+ * </pre>
+ *
+ * <p>
+ * The following will take a stream of strings and concatenates them into a single string:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
+ * }
+ * </pre>
+ *
+ * @param <R> type of the result
+ * @param <A> Type of the accumulator.
+ * @param supplier a function that creates a new result container. For a parallel execution, this function may be called
+ * multiple times and must return a fresh value each time.
+ * @param accumulator An associative, non-interfering, stateless function for incorporating an additional element into a
+ * result
+ * @param combiner An associative, non-interfering, stateless function for combining two values, which must be
+ * compatible with the accumulator function
+ * @return The result of the reduction
+ */
+ public <A, R> R collect(final Supplier<R> supplier, final BiConsumer<R, ? super T> accumulator, final BiConsumer<R, R> combiner) {
+ makeTerminated();
+ return stream().collect(supplier, accumulator, combiner);
+ }
+
+ /**
+ * Returns a FailableStream consisting of the elements of this stream that match the given FailablePredicate.
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * @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<T> filter(final FailablePredicate<T, ?> predicate) {
+ assertNotTerminated();
+ stream = stream.filter(Failable.asPredicate(predicate));
+ return this;
+ }
+
+ /**
+ * Performs an action for each element of this stream.
+ *
+ * <p>
+ * This is a terminal operation.
+ * </p>
+ *
+ * <p>
+ * The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does
+ * <em>not</em> 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.
+ * </p>
+ *
+ * @param action a non-interfering action to perform on the elements
+ */
+ public void forEach(final FailableConsumer<T, ?> action) {
+ makeTerminated();
+ stream().forEach(Failable.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.
+ *
+ * <p>
+ * This is an intermediate operation.
+ * </p>
+ *
+ * @param <R> The element type of the new stream
+ * @param mapper A non-interfering, stateless function to apply to each element
+ * @return the new stream
+ */
+ public <R> FailableStream<R> map(final FailableFunction<T, R, ?> mapper) {
+ assertNotTerminated();
+ return new FailableStream<>(stream.map(Failable.asFunction(mapper)));
+ }
+
+ /**
+ * Performs a reduction on the elements of this stream, using the provided identity value and an associative
+ * accumulation function, and returns the reduced value. This is equivalent to:
+ *
+ * <pre>
+ * {@code
+ * T result = identity;
+ * for (T element : this stream)
+ * result = accumulator.apply(result, element)
+ * return result;
+ * }
+ * </pre>
+ *
+ * but is not constrained to execute sequentially.
+ *
+ * <p>
+ * The {@code identity} value must be an identity for the accumulator function. This means that for all {@code t},
+ * {@code accumulator.apply(identity, t)} is equal to {@code t}. The {@code accumulator} function must be an associative
+ * function.
+ * </p>
+ *
+ * <p>
+ * This is a terminal operation.
+ * </p>
+ *
+ * Note Sum, min, max, average, and string concatenation are all special cases of reduction. Summing a stream of numbers
+ * can be expressed as:
+ *
+ * <pre>
+ * {@code
+ * Integer sum = integers.reduce(0, (a, b) -> a + b);
+ * }
+ * </pre>
+ *
+ * or:
+ *
+ * <pre>
+ * {@code
+ * Integer sum = integers.reduce(0, Integer::sum);
+ * }
+ * </pre>
+ *
+ * <p>
+ * While this may seem a more roundabout way to perform an aggregation compared to simply mutating a running total in a
+ * loop, reduction operations parallelize more gracefully, without needing additional synchronization and with greatly
+ * reduced risk of data races.
+ * </p>
+ *
+ * @param identity the identity value for the accumulating function
+ * @param accumulator an associative, non-interfering, stateless function for combining two values
+ * @return the result of the reduction
+ */
+ public T reduce(final T identity, final BinaryOperator<T> accumulator) {
+ makeTerminated();
+ return stream().reduce(identity, accumulator);
+ }
+
+ /**
+ * Converts the FailableStream into an equivalent stream.
+ *
+ * @return A stream, which will return the same elements, which this FailableStream would return.
+ */
+ public Stream<T> stream() {
+ return stream;
+ }
+ }
+
+ /**
+ * Converts the given {@link Collection} 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:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final Function<O, String> mapper = (o) -> {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * final List<String> strList = list.stream().map(mapper).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * as follows:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * While the second version may not be <em>quite</em> 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 <T> The streams element type.
+ * @param stream The stream, which is being converted.
+ * @return The {@link FailableStream}, which has been created by converting the stream.
+ * @since 3.13.0
+ */
+ public static <T> FailableStream<T> failableStream(final Collection<T> stream) {
+ return failableStream(of(stream));
+ }
+
+ /**
+ * 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:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final Function<O, String> mapper = (o) -> {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * final List<String> strList = list.stream().map(mapper).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * as follows:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * While the second version may not be <em>quite</em> 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 <T> The streams element type.
+ * @param stream The stream, which is being converted.
+ * @return The {@link FailableStream}, which has been created by converting the stream.
+ * @since 3.13.0
+ */
+ public static <T> FailableStream<T> failableStream(final Stream<T> stream) {
+ return new FailableStream<>(stream);
+ }
+
+ /**
+ * Streams only instances of the give Class in a collection.
+ * <p>
+ * This method shorthand for:
+ * </p>
+ * <pre>
+ * {@code (Stream<E>) Streams.toStream(collection).filter(collection, SomeClass.class::isInstance);}
+ * </pre>
+ *
+ * @param <E> the type of elements in the collection we want to stream.
+ * @param clazz the type of elements in the collection we want to stream.
+ * @param collection the collection to stream or null.
+ * @return A non-null stream that only provides instances we want.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> instancesOf(final Class<? super E> clazz, final Collection<? super E> collection) {
+ return instancesOf(clazz, of(collection));
+ }
+
+ @SuppressWarnings("unchecked") // After the isInstance check, we still need to type-cast.
+ private static <E> Stream<E> instancesOf(final Class<? super E> clazz, final Stream<?> stream) {
+ return (Stream<E>) of(stream).filter(clazz::isInstance);
+ }
+
+ /**
+ * Streams the non-null elements of a collection.
+ *
+ * @param <E> the type of elements in the collection.
+ * @param collection the collection to stream or null.
+ * @return A non-null stream that filters out null elements.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> nonNull(final Collection<E> collection) {
+ return of(collection).filter(Objects::nonNull);
+ }
+
+ /**
+ * Streams the non-null elements of an array.
+ *
+ * @param <E> the type of elements in the collection.
+ * @param array the array to stream or null.
+ * @return A non-null stream that filters out null elements.
+ * @since 3.13.0
+ */
+ @SafeVarargs
+ public static <E> Stream<E> nonNull(final E... array) {
+ return nonNull(of(array));
+ }
+
+ /**
+ * Streams the non-null elements of a stream.
+ *
+ * @param <E> the type of elements in the collection.
+ * @param stream the stream to stream or null.
+ * @return A non-null stream that filters out null elements.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> nonNull(final Stream<E> stream) {
+ return of(stream).filter(Objects::nonNull);
+ }
+
+ /**
+ * Delegates to {@link Collection#stream()} or returns {@link Stream#empty()} if the collection is null.
+ *
+ * @param <E> the type of elements in the collection.
+ * @param collection the collection to stream or null.
+ * @return {@link Collection#stream()} or {@link Stream#empty()} if the collection is null.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> of(final Collection<E> collection) {
+ return collection == null ? Stream.empty() : collection.stream();
+ }
+
+ /**
+ * Streams the elements of the given enumeration in order.
+ *
+ * @param <E> The enumeration element type.
+ * @param enumeration The enumeration to stream.
+ * @return a new stream.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> of(final Enumeration<E> enumeration) {
+ return StreamSupport.stream(new EnumerationSpliterator<>(Long.MAX_VALUE, Spliterator.ORDERED, enumeration), false);
+ }
+
+ /**
+ * Creates a stream on the given Iterable.
+ *
+ * @param <E> the type of elements in the Iterable.
+ * @param iterable the Iterable to stream or null.
+ * @return a new Stream or {@link Stream#empty()} if the Iterable is null.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> of(final Iterable<E> iterable) {
+ return iterable == null ? Stream.empty() : StreamSupport.stream(iterable.spliterator(), false);
+ }
+
+ /**
+ * Creates a stream on the given Iterator.
+ *
+ * @param <E> the type of elements in the Iterator.
+ * @param iterator the Iterator to stream or null.
+ * @return a new Stream or {@link Stream#empty()} if the Iterator is null.
+ * @since 3.13.0
+ */
+ public static <E> Stream<E> of(final Iterator<E> iterator) {
+ return iterator == null ? Stream.empty() : StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
+ }
+
+ /**
+ * Returns the stream or {@link Stream#empty()} if the stream is null.
+ *
+ * @param <E> the type of elements in the collection.
+ * @param stream the stream to stream or null.
+ * @return the stream or {@link Stream#empty()} if the stream is null.
+ * @since 3.13.0
+ */
+ private static <E> Stream<E> of(final Stream<E> stream) {
+ return stream == null ? Stream.empty() : stream;
+ }
+
+ /**
+ * Null-safe version of {@link Stream#of(Object[])}.
+ *
+ * @param <T> the type of stream elements.
+ * @param values the elements of the new stream, may be {@code null}.
+ * @return the new stream on {@code values} or {@link Stream#empty()}.
+ * @since 3.13.0
+ */
+ @SafeVarargs // Creating a stream from an array is safe
+ public static <T> Stream<T> of(final T... values) {
+ return values == null ? Stream.empty() : Stream.of(values);
+ }
+
+ /**
+ * Converts the given {@link Collection} 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:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final Function<O, String> mapper = (o) -> {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * final List<String> strList = list.stream().map(mapper).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * as follows:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * While the second version may not be <em>quite</em> 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 <E> The streams element type.
+ * @param collection The stream, which is being converted.
+ * @return The {@link FailableStream}, which has been created by converting the stream.
+ * @deprecated Use {@link #failableStream(Collection)}.
+ */
+ @Deprecated
+ public static <E> FailableStream<E> stream(final Collection<E> collection) {
+ return failableStream(collection);
+ }
+
+ /**
+ * 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:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final Function<O, String> mapper = (o) -> {
+ * try {
+ * return (String) m.invoke(o);
+ * } catch (Throwable t) {
+ * throw Failable.rethrow(t);
+ * }
+ * };
+ * final List<String> strList = list.stream().map(mapper).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * as follows:
+ *
+ * <pre>
+ * {@code
+ * final List<O> list;
+ * final Method m;
+ * final List<String> strList = Failable.stream(list.stream()).map((o) -> (String) m.invoke(o)).collect(Collectors.toList());
+ * }
+ * </pre>
+ *
+ * While the second version may not be <em>quite</em> 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 <T> 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 Use {@link #failableStream(Stream)}.
+ */
+ @Deprecated
+ public static <T> FailableStream<T> stream(final Stream<T> stream) {
+ return failableStream(stream);
+ }
+
+ /**
+ * Returns a {@link Collector} that accumulates the input elements into a new array.
+ *
+ * @param pElementType Type of an element in the array.
+ * @param <T> the type of the input elements
+ * @return a {@link Collector} which collects all the input elements into an array, in encounter order
+ */
+ public static <T> Collector<T, ?, T[]> toArray(final Class<T> pElementType) {
+ return new ArrayCollector<>(pElementType);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/stream/package-info.java b/src/main/java/org/apache/commons/lang3/stream/package-info.java
new file mode 100644
index 000000000..b2deefcfb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/stream/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides utility classes to complement those in {@code java.util.stream}.
+ *
+ * <p>Contains utilities to allow streaming of failable functional interfaces from the
+ * {@code org.apache.commons.lang3.functions} package allowing streaming of functional expressions
+ * that may raise an Exception.
+ *
+ * @since 3.11
+ */
+package org.apache.commons.lang3.stream;
diff --git a/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java b/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java
new file mode 100644
index 000000000..a80cd60ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/CompositeFormat.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+
+/**
+ * Formats using one formatter and parses using a different formatter. An
+ * example of use for this would be a webapp where data is taken in one way and
+ * stored in a database another way.
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/CompositeFormat.html">
+ * CompositeFormat</a> instead
+ */
+@Deprecated
+public class CompositeFormat extends Format {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -4329119827877627683L;
+
+ /** The parser to use. */
+ private final Format parser;
+ /** The formatter to use. */
+ private final Format formatter;
+
+ /**
+ * Create a format that points its parseObject method to one implementation
+ * and its format method to another.
+ *
+ * @param parser implementation
+ * @param formatter implementation
+ */
+ public CompositeFormat(final Format parser, final Format formatter) {
+ this.parser = parser;
+ this.formatter = formatter;
+ }
+
+ /**
+ * Uses the formatter Format instance.
+ *
+ * @param obj the object to format
+ * @param toAppendTo the {@link StringBuffer} to append to
+ * @param pos the FieldPosition to use (or ignore).
+ * @return {@code toAppendTo}
+ * @see Format#format(Object, StringBuffer, FieldPosition)
+ */
+ @Override // Therefore has to use StringBuffer
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo,
+ final FieldPosition pos) {
+ return formatter.format(obj, toAppendTo, pos);
+ }
+
+ /**
+ * Uses the parser Format instance.
+ *
+ * @param source the String source
+ * @param pos the ParsePosition containing the position to parse from, will
+ * be updated according to parsing success (index) or failure
+ * (error index)
+ * @return the parsed Object
+ * @see Format#parseObject(String, ParsePosition)
+ */
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ 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.
+ *
+ * @param input String to reformat
+ * @return A reformatted String
+ * @throws ParseException thrown by parseObject(String) call
+ */
+ public String reformat(final String input) throws ParseException {
+ return format(parseObject(input));
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java
new file mode 100644
index 000000000..df8a19cec
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Extends {@code java.text.MessageFormat} to allow pluggable/additional formatting
+ * options for embedded format elements. Client code should specify a registry
+ * of {@link FormatFactory} instances associated with {@link String}
+ * format names. This registry will be consulted when the format elements are
+ * parsed from the message pattern. In this way custom patterns can be specified,
+ * and the formats supported by {@code java.text.MessageFormat} can be overridden
+ * at the format and/or format style level (see MessageFormat). A "format element"
+ * embedded in the message pattern is specified (<b>()?</b> signifies optionality):<br>
+ * <code>{</code><i>argument-number</i><b>(</b>{@code ,}<i>format-name</i><b>
+ * (</b>{@code ,}<i>format-style</i><b>)?)?</b><code>}</code>
+ *
+ * <p>
+ * <i>format-name</i> and <i>format-style</i> values are trimmed of surrounding whitespace
+ * in the manner of {@code java.text.MessageFormat}. If <i>format-name</i> denotes
+ * {@code FormatFactory formatFactoryInstance} in {@code registry}, a {@link Format}
+ * matching <i>format-name</i> and <i>format-style</i> is requested from
+ * {@code formatFactoryInstance}. If this is successful, the {@link Format}
+ * found is used for this format element.
+ * </p>
+ *
+ * <p><b>NOTICE:</b> The various subformat mutator methods are considered unnecessary; they exist on the parent
+ * class to allow the type of customization which it is the job of this class to provide in
+ * a configurable fashion. These methods have thus been disabled and will throw
+ * {@link UnsupportedOperationException} if called.
+ * </p>
+ *
+ * <p>Limitations inherited from {@code java.text.MessageFormat}:</p>
+ * <ul>
+ * <li>When using "choice" subformats, support for nested formatting instructions is limited
+ * to that provided by the base class.</li>
+ * <li>Thread-safety of {@link Format}s, including {@link MessageFormat} and thus
+ * {@link ExtendedMessageFormat}, is not guaranteed.</li>
+ * </ul>
+ *
+ * @since 2.4
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/ExtendedMessageFormat.html">
+ * ExtendedMessageFormat</a> instead
+ */
+@Deprecated
+public class ExtendedMessageFormat extends MessageFormat {
+ private static final long serialVersionUID = -2362048321261811743L;
+ private static final int HASH_SEED = 31;
+
+ private static final String DUMMY_PATTERN = "";
+ private static final char START_FMT = ',';
+ private static final char END_FE = '}';
+ private static final char START_FE = '{';
+ private static final char QUOTE = '\'';
+
+ /**
+ * To pattern string.
+ */
+ private String toPattern;
+
+ /**
+ * Our registry of FormatFactory.
+ */
+ private final Map<String, ? extends FormatFactory> registry;
+
+ /**
+ * Create a new ExtendedMessageFormat for the default locale.
+ *
+ * @param pattern the pattern to use, not null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern) {
+ this(pattern, Locale.getDefault());
+ }
+
+ /**
+ * Create a new ExtendedMessageFormat.
+ *
+ * @param pattern the pattern to use, not null
+ * @param locale the locale to use, not null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern, final Locale locale) {
+ 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<String, ? extends FormatFactory> registry) {
+ this(pattern, Locale.getDefault(), registry);
+ }
+
+ /**
+ * Create a new ExtendedMessageFormat.
+ *
+ * @param pattern the pattern to use, not null.
+ * @param locale the locale to use.
+ * @param registry the registry of format factories, may be null.
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> registry) {
+ super(DUMMY_PATTERN);
+ setLocale(LocaleUtils.toLocale(locale));
+ this.registry = registry;
+ applyPattern(pattern);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPattern() {
+ return toPattern;
+ }
+
+ /**
+ * Apply the specified pattern.
+ *
+ * @param pattern String
+ */
+ @Override
+ public final void applyPattern(final String pattern) {
+ if (registry == null) {
+ super.applyPattern(pattern);
+ toPattern = super.toPattern();
+ return;
+ }
+ final ArrayList<Format> foundFormats = new ArrayList<>();
+ final ArrayList<String> foundDescriptions = new ArrayList<>();
+ final StringBuilder stripCustom = new StringBuilder(pattern.length());
+
+ final ParsePosition pos = new ParsePosition(0);
+ final char[] c = pattern.toCharArray();
+ int fmtCount = 0;
+ while (pos.getIndex() < pattern.length()) {
+ switch (c[pos.getIndex()]) {
+ case QUOTE:
+ appendQuotedString(pattern, pos, stripCustom);
+ break;
+ case START_FE:
+ fmtCount++;
+ seekNonWs(pattern, pos);
+ final int start = pos.getIndex();
+ final int index = readArgumentIndex(pattern, next(pos));
+ stripCustom.append(START_FE).append(index);
+ seekNonWs(pattern, pos);
+ Format format = null;
+ String formatDescription = null;
+ if (c[pos.getIndex()] == START_FMT) {
+ formatDescription = parseFormatDescription(pattern,
+ next(pos));
+ format = getFormat(formatDescription);
+ if (format == null) {
+ stripCustom.append(START_FMT).append(formatDescription);
+ }
+ }
+ foundFormats.add(format);
+ foundDescriptions.add(format == null ? null : formatDescription);
+ Validate.isTrue(foundFormats.size() == fmtCount);
+ Validate.isTrue(foundDescriptions.size() == fmtCount);
+ if (c[pos.getIndex()] != END_FE) {
+ throw new IllegalArgumentException(
+ "Unreadable format element at position " + start);
+ }
+ //$FALL-THROUGH$
+ default:
+ stripCustom.append(c[pos.getIndex()]);
+ next(pos);
+ }
+ }
+ super.applyPattern(stripCustom.toString());
+ toPattern = insertFormats(super.toPattern(), foundDescriptions);
+ if (containsElements(foundFormats)) {
+ final Format[] origFormats = getFormats();
+ // only loop over what we know we have, as MessageFormat on Java 1.3
+ // seems to provide an extra format element:
+ int i = 0;
+ for (final Format f : foundFormats) {
+ if (f != null) {
+ origFormats[i] = f;
+ }
+ i++;
+ }
+ super.setFormats(origFormats);
+ }
+ }
+
+ /**
+ * 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
+ */
+ @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();
+ }
+
+ /**
+ * Check if this extended message format is equal to another object.
+ *
+ * @param obj the object to compare to
+ * @return true if this object equals the other, otherwise false
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (ObjectUtils.notEqual(getClass(), obj.getClass())) {
+ return false;
+ }
+ final ExtendedMessageFormat rhs = (ExtendedMessageFormat) obj;
+ if (ObjectUtils.notEqual(toPattern, rhs.toPattern)) {
+ return false;
+ }
+ 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.
+ *
+ * @param desc String
+ * @return Format
+ */
+ private Format getFormat(final String desc) {
+ if (registry != null) {
+ String name = desc;
+ String args = null;
+ final int i = desc.indexOf(START_FMT);
+ if (i > 0) {
+ name = desc.substring(0, i).trim();
+ args = desc.substring(i + 1).trim();
+ }
+ final FormatFactory factory = registry.get(name);
+ if (factory != null) {
+ return factory.getFormat(name, args, getLocale());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Insert formats back into the pattern for toPattern() support.
+ *
+ * @param pattern source
+ * @param customPatterns The custom patterns to re-insert, if any
+ * @return full pattern
+ */
+ private String insertFormats(final String pattern, final ArrayList<String> customPatterns) {
+ if (!containsElements(customPatterns)) {
+ return pattern;
+ }
+ final StringBuilder sb = new StringBuilder(pattern.length() * 2);
+ final ParsePosition pos = new ParsePosition(0);
+ int fe = -1;
+ int depth = 0;
+ while (pos.getIndex() < pattern.length()) {
+ final char c = pattern.charAt(pos.getIndex());
+ switch (c) {
+ case QUOTE:
+ appendQuotedString(pattern, pos, sb);
+ break;
+ case START_FE:
+ depth++;
+ sb.append(START_FE).append(readArgumentIndex(pattern, next(pos)));
+ // do not look for custom patterns when they are embedded, e.g. in a choice
+ if (depth == 1) {
+ fe++;
+ final String customPattern = customPatterns.get(fe);
+ if (customPattern != null) {
+ sb.append(START_FMT).append(customPattern);
+ }
+ }
+ break;
+ case END_FE:
+ depth--;
+ //$FALL-THROUGH$
+ default:
+ sb.append(c);
+ next(pos);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Consume whitespace from the current parse position.
+ *
+ * @param pattern String to read
+ * @param pos current position
+ */
+ private void seekNonWs(final String pattern, final ParsePosition pos) {
+ int len;
+ final char[] buffer = pattern.toCharArray();
+ do {
+ len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
+ pos.setIndex(pos.getIndex() + len);
+ } while (len > 0 && pos.getIndex() < pattern.length());
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Consume quoted string only
+ *
+ * @param pattern pattern to parse
+ * @param pos current parse position
+ */
+ private void getQuotedString(final String pattern, final ParsePosition pos) {
+ appendQuotedString(pattern, pos, null);
+ }
+
+ /**
+ * Learn whether the specified Collection contains non-null elements.
+ * @param coll to check
+ * @return {@code true} if some Object was found, {@code false} otherwise.
+ */
+ private boolean containsElements(final Collection<?> coll) {
+ if (coll == null || coll.isEmpty()) {
+ return false;
+ }
+ return coll.stream().anyMatch(Objects::nonNull);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/FormatFactory.java b/src/main/java/org/apache/commons/lang3/text/FormatFactory.java
new file mode 100644
index 000000000..ed5e8c3e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/FormatFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.text.Format;
+import java.util.Locale;
+
+/**
+ * Format factory.
+ *
+ * @since 2.4
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/FormatFactory.html">
+ * FormatFactory</a> instead
+ */
+@Deprecated
+public interface FormatFactory {
+
+ /**
+ * Create or retrieve a format instance.
+ *
+ * @param name The format type name
+ * @param arguments Arguments used to create the format instance. This allows the
+ * {@link FormatFactory} to implement the "format style"
+ * concept from {@code java.text.MessageFormat}.
+ * @param locale The locale, may be null
+ * @return The format instance
+ */
+ Format getFormat(String name, String arguments, Locale locale);
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java b/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java
new file mode 100644
index 000000000..1954ecf2a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/FormattableUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import static java.util.FormattableFlags.LEFT_JUSTIFY;
+
+import java.util.Formattable;
+import java.util.Formatter;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Provides utilities for working with the {@link Formattable} interface.
+ *
+ * <p>The {@link Formattable} interface provides basic control over formatting
+ * when using a {@link Formatter}. It is primarily concerned with numeric precision
+ * and padding, and is not designed to allow generalised alternate formats.</p>
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/FormattableUtils.html">
+ * FormattableUtils</a> instead
+ */
+@Deprecated
+public class FormattableUtils {
+
+ /**
+ * A format that simply outputs the value as a string.
+ */
+ 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.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ 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
+ * 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}
+ * @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) {
+ return append(seq, formatter, flags, width, precision, ' ', null);
+ }
+
+ /**
+ * Handles the common {@link Formattable} operations of truncate-pad-append,
+ * with no ellipsis on precision overflow.
+ *
+ * @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 padChar the pad character to use
+ * @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 char padChar) {
+ 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.
+ *
+ * @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 padChar the pad character to use
+ * @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 char padChar, final CharSequence ellipsis) {
+ Validate.isTrue(ellipsis == null || precision < 0 || ellipsis.length() <= precision,
+ "Specified ellipsis '%1$s' exceeds precision of %2$s", ellipsis, Integer.valueOf(precision));
+ final StringBuilder buf = new StringBuilder(seq);
+ if (precision >= 0 && precision < seq.length()) {
+ final CharSequence actualEllipsis = ObjectUtils.defaultIfNull(ellipsis, StringUtils.EMPTY);
+ buf.replace(precision - actualEllipsis.length(), seq.length(), actualEllipsis.toString());
+ }
+ final boolean leftJustify = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY;
+ for (int i = buf.length(); i < width; i++) {
+ buf.insert(leftJustify ? i : 0, padChar);
+ }
+ formatter.format(buf.toString());
+ return formatter;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java
new file mode 100644
index 000000000..b79bd9d88
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java
@@ -0,0 +1,3065 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.nio.CharBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.CharUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.Builder;
+
+/**
+ * Builds a string from constituent parts providing a more flexible and powerful API
+ * than StringBuffer.
+ * <p>
+ * The main differences from StringBuffer/StringBuilder are:
+ * </p>
+ * <ul>
+ * <li>Not synchronized</li>
+ * <li>Not final</li>
+ * <li>Subclasses have direct access to character array</li>
+ * <li>Additional methods
+ * <ul>
+ * <li>appendWithSeparators - adds an array of values, with a separator</li>
+ * <li>appendPadding - adds a length padding characters</li>
+ * <li>appendFixedLength - adds a fixed width field to the builder</li>
+ * <li>toCharArray/getChars - simpler ways to get a range of the character array</li>
+ * <li>delete - delete char or string</li>
+ * <li>replace - search and replace for a char or string</li>
+ * <li>leftString/rightString/midString - substring without exceptions</li>
+ * <li>contains - whether the builder contains a char or string</li>
+ * <li>size/clear/isEmpty - collections style API methods</li>
+ * </ul>
+ * </li>
+ * <li>Views
+ * <ul>
+ * <li>asTokenizer - uses the internal buffer as the source of a StrTokenizer</li>
+ * <li>asReader - uses the internal buffer as the source of a Reader</li>
+ * <li>asWriter - allows a Writer to write directly to the internal buffer</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>
+ * The aim has been to provide an API that mimics very closely what StringBuffer
+ * provides, but with additional methods. It should be noted that some edge cases,
+ * with invalid indices or null input, have been altered - see individual methods.
+ * The biggest of these changes is that by default, null will not output the text
+ * 'null'. This can be controlled by a property, {@link #setNullText(String)}.
+ * </p>
+ * <p>
+ * Prior to 3.0, this class implemented Cloneable but did not implement the
+ * clone method so could not be used. From 3.0 onwards it no longer implements
+ * the interface.
+ * </p>
+ *
+ * @since 2.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/TextStringBuilder.html">
+ * TextStringBuilder</a> instead
+ */
+@Deprecated
+public class StrBuilder implements CharSequence, Appendable, Serializable, Builder<String> {
+
+ /**
+ * 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;
+
+ /**
+ * Constructor that creates an empty builder initial capacity 32 characters.
+ */
+ public StrBuilder() {
+ this(CAPACITY);
+ }
+
+ /**
+ * Constructor that creates an empty builder the specified initial capacity.
+ *
+ * @param initialCapacity the initial capacity, zero or less will be converted to 32
+ */
+ public StrBuilder(int initialCapacity) {
+ if (initialCapacity <= 0) {
+ initialCapacity = CAPACITY;
+ }
+ buffer = new char[initialCapacity];
+ }
+
+ /**
+ * Constructor that creates a builder from the string, allocating
+ * 32 extra characters for growth.
+ *
+ * @param str the string to copy, null treated as blank string
+ */
+ public StrBuilder(final String str) {
+ if (str == null) {
+ buffer = new char[CAPACITY];
+ } else {
+ buffer = new char[str.length() + CAPACITY];
+ append(str);
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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.
+ * <p>
+ * This method is the same as {@link #length()} and is provided to match the
+ * API of Collections.
+ * </p>
+ *
+ * @return the length
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Checks is the string builder is empty (convenience Collections API style method).
+ * <p>
+ * This method is the same as checking {@link #length()} and is provided to match the
+ * API of Collections.
+ * </p>
+ *
+ * @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).
+ * <p>
+ * This method is the same as checking {@link #length()} and is provided to match the
+ * API of Collections.
+ * </p>
+ *
+ * @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).
+ * <p>
+ * This method does not reduce the size of the internal character buffer.
+ * To do that, call {@code clear()} followed by {@link #minimizeCapacity()}.
+ * </p>
+ * <p>
+ * This method is the same as {@link #setLength(int)} called with zero
+ * and is provided to match the API of Collections.
+ * </p>
+ *
+ * @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;
+ } 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;
+ }
+ }
+ return size - oldSize;
+ }
+
+ /**
+ * Appends the new line string to this string builder.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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
+ * @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 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 the contents of a char buffer to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param buf the char buffer to append
+ * @return this, to enable chaining
+ * @since 3.4
+ */
+ public StrBuilder append(final CharBuffer buf) {
+ if (buf == null) {
+ return appendNull();
+ }
+ if (buf.hasArray()) {
+ final int length = buf.remaining();
+ final int len = length();
+ ensureCapacity(len + length);
+ System.arraycopy(buf.array(), buf.arrayOffset() + buf.position(), buffer, len, length);
+ size += length;
+ } else {
+ append(buf.toString());
+ }
+ return this;
+ }
+
+ /**
+ * Appends the contents of a char buffer to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param buf the char buffer 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.4
+ */
+ public StrBuilder append(final CharBuffer buf, final int startIndex, final int length) {
+ if (buf == null) {
+ return appendNull();
+ }
+ if (buf.hasArray()) {
+ final int totalLength = buf.remaining();
+ if (startIndex < 0 || startIndex > totalLength) {
+ throw new StringIndexOutOfBoundsException("startIndex must be valid");
+ }
+ if (length < 0 || startIndex + length > totalLength) {
+ throw new StringIndexOutOfBoundsException("length must be valid");
+ }
+ final int len = length();
+ ensureCapacity(len + length);
+ System.arraycopy(buf.array(), buf.arrayOffset() + buf.position() + startIndex, buffer, len, length);
+ size += length;
+ } else {
+ append(buf.toString(), startIndex, length);
+ }
+ return this;
+ }
+
+ /**
+ * Appends a string buffer to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the string buffer to append
+ * @return this, to enable chaining
+ */
+ public StrBuilder append(final StringBuffer 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 buffer 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 StringBuffer 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 StringBuilder to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the StringBuilder to append
+ * @return this, to enable chaining
+ * @since 3.2
+ */
+ public StrBuilder append(final StringBuilder 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 StringBuilder to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the StringBuilder 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.2
+ */
+ public StrBuilder append(final StringBuilder 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 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 char array to the string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param chars the char array to append
+ * @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;
+ }
+ return this;
+ }
+
+ /**
+ * Appends a char array 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
+ */
+ 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;
+ }
+ return this;
+ }
+
+ /**
+ * Appends a boolean value to the string builder.
+ *
+ * @param value the value to append
+ * @return this, to enable chaining
+ */
+ 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;
+ }
+
+ /**
+ * Appends a char value to the string builder.
+ *
+ * @param ch the value to append
+ * @return this, to enable chaining
+ * @since 3.0
+ */
+ @Override
+ public StrBuilder append(final char ch) {
+ final int len = length();
+ ensureCapacity(len + 1);
+ buffer[size++] = ch;
+ return this;
+ }
+
+ /**
+ * 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 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 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 an object followed by a new line to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param obj the object to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final Object obj) {
+ return append(obj).appendNewLine();
+ }
+
+ /**
+ * Appends a string followed by a new line to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the string to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final String str) {
+ return append(str).appendNewLine();
+ }
+
+ /**
+ * Appends part of a string 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 String str, final int startIndex, final int length) {
+ return append(str, startIndex, length).appendNewLine();
+ }
+
+ /**
+ * 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 appendln(final String format, final Object... objs) {
+ return append(format, objs).appendNewLine();
+ }
+
+ /**
+ * Appends a string buffer followed by a new line to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the string buffer to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final StringBuffer str) {
+ return append(str).appendNewLine();
+ }
+
+ /**
+ * Appends a 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 3.2
+ */
+ public StrBuilder appendln(final StringBuilder 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 builder 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.2
+ */
+ public StrBuilder appendln(final StringBuilder str, final int startIndex, final int length) {
+ return append(str, startIndex, length).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 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 <T> the element type
+ * @param array the array to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public <T> 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (Iterator it = list.iterator(); it.hasNext(); ) {
+ * appendSeparator(",");
+ * append(it.next());
+ * }
+ * </pre>
+ * <p>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ * </p>
+ *
+ * @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);
+ }
+
+ /**
+ * 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
+ *
+ * Appending a null separator will have no effect.
+ * The separator is appended using {@link #append(String)}.
+ * <p>
+ * This method is for example useful for constructing queries
+ * </p>
+ * <pre>
+ * 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)
+ * </pre>
+ *
+ * @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);
+ }
+ return this;
+ }
+
+ /**
+ * Appends a separator if the builder is currently non-empty.
+ * The separator is appended using {@link #append(char)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (Iterator it = list.iterator(); it.hasNext(); ) {
+ * appendSeparator(',');
+ * append(it.next());
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendSeparator(final char separator) {
+ if (isNotEmpty()) {
+ append(separator);
+ }
+ return this;
+ }
+
+ /**
+ * Append one of both separators to the builder
+ * If the builder is currently empty it will append the defaultIfEmpty-separator
+ * Otherwise it will append the standard-separator
+ *
+ * The separator is appended using {@link #append(char)}.
+ * @param standard the separator if builder is not empty
+ * @param defaultIfEmpty the separator if builder is empty
+ * @return this, to enable chaining
+ * @since 2.5
+ */
+ public StrBuilder appendSeparator(final char standard, final char defaultIfEmpty) {
+ if (isNotEmpty()) {
+ append(standard);
+ } else {
+ append(defaultIfEmpty);
+ }
+ 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (int i = 0; i &lt; list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ * </pre>
+ * 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.
+ * The separator is appended using {@link #append(char)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (int i = 0; i &lt; list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use
+ * @param loopIndex the loop index
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendSeparator(final char separator, final int loopIndex) {
+ if (loopIndex > 0) {
+ append(separator);
+ }
+ return this;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 = obj == null ? getNullText() : obj.toString();
+ 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.
+ *
+ * @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 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 = obj == null ? getNullText() : obj.toString();
+ 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;
+ }
+ 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;
+ }
+
+ /**
+ * 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(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 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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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);
+ }
+ }
+
+ /**
+ * 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 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
+ * @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 this;
+ }
+
+ /**
+ * 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 search string with the replace string throughout the builder.
+ *
+ * @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 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;
+ }
+
+ /**
+ * 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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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 replaceAll(final StrMatcher matcher, final String replaceStr) {
+ return replace(matcher, replaceStr, 0, size, -1);
+ }
+
+ /**
+ * Replaces the first match within the builder with the replace string.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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);
+ }
+
+ /**
+ * Advanced search and replaces within the builder using a matcher.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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 within the builder using a matcher.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * Note that this method handles null input quietly, unlike String.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * Note that this method handles null input quietly, unlike String.
+ * </p>
+ *
+ * @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
+ */
+ @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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);
+ }
+
+ /**
+ * Extracts the leftmost characters from the string builder without
+ * throwing an exception.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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);
+ }
+
+ /**
+ * Checks if the string builder contains the specified char.
+ *
+ * @param ch the character to find
+ * @return true if the builder contains the character
+ */
+ public boolean contains(final char ch) {
+ final char[] thisBuf = buffer;
+ for (int i = 0; i < this.size; i++) {
+ if (thisBuf[i] == ch) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the string builder contains the specified string.
+ *
+ * @param str the string to find
+ * @return true if the builder contains the string
+ */
+ public boolean contains(final String str) {
+ return indexOf(str, 0) >= 0;
+ }
+
+ /**
+ * Checks if the string builder contains a string matched using the
+ * specified matcher.
+ * <p>
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to search for the character
+ * 'a' followed by a number.
+ * </p>
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @return true if the matcher finds a match in the builder
+ */
+ public boolean contains(final StrMatcher matcher) {
+ return indexOf(matcher, 0) >= 0;
+ }
+
+ /**
+ * Searches the string builder to find the first reference to the specified char.
+ *
+ * @param ch the character to find
+ * @return the first index of the character, or -1 if not found
+ */
+ public int indexOf(final char ch) {
+ return indexOf(ch, 0);
+ }
+
+ /**
+ * Searches the string builder to find the first reference to the specified char.
+ *
+ * @param ch the character to find
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the first index of the character, or -1 if not found
+ */
+ public int indexOf(final char ch, int startIndex) {
+ startIndex = Math.max(startIndex, 0);
+ if (startIndex >= size) {
+ return -1;
+ }
+ final char[] thisBuf = buffer;
+ for (int i = startIndex; i < size; i++) {
+ if (thisBuf[i] == ch) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Searches the string builder to find the first reference to the specified string.
+ * <p>
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * </p>
+ *
+ * @param str the string to find, null returns -1
+ * @return the first index of the string, or -1 if not found
+ */
+ public int indexOf(final String str) {
+ return indexOf(str, 0);
+ }
+
+ /**
+ * Searches the string builder to find the first reference to the specified
+ * string starting searching from the given index.
+ * <p>
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * </p>
+ *
+ * @param str the string to find, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the first index of the string, or -1 if not found
+ */
+ public int indexOf(final String str, final int startIndex) {
+ return StringUtils.indexOf(this, str, startIndex);
+ }
+
+ /**
+ * Searches the string builder using the matcher to find the first match.
+ * <p>
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ * </p>
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @return the first index matched, or -1 if not found
+ */
+ public int indexOf(final StrMatcher matcher) {
+ return indexOf(matcher, 0);
+ }
+
+ /**
+ * Searches the string builder using the matcher to find the first
+ * match searching from the given index.
+ * <p>
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ * </p>
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the first index matched, or -1 if not found
+ */
+ public int indexOf(final StrMatcher matcher, int startIndex) {
+ startIndex = Math.max(startIndex, 0);
+ if (matcher == null || startIndex >= size) {
+ return -1;
+ }
+ final int len = size;
+ final char[] buf = buffer;
+ for (int i = startIndex; i < len; i++) {
+ if (matcher.isMatch(buf, i, startIndex, len) > 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Searches the string builder to find the last reference to the specified char.
+ *
+ * @param ch the character to find
+ * @return the last index of the character, or -1 if not found
+ */
+ public int lastIndexOf(final char ch) {
+ return lastIndexOf(ch, size - 1);
+ }
+
+ /**
+ * Searches the string builder to find the last reference to the specified char.
+ *
+ * @param ch the character to find
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the last index of the character, or -1 if not found
+ */
+ public int lastIndexOf(final char ch, int startIndex) {
+ startIndex = startIndex >= size ? size - 1 : startIndex;
+ if (startIndex < 0) {
+ return -1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (buffer[i] == ch) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Searches the string builder to find the last reference to the specified string.
+ * <p>
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * </p>
+ *
+ * @param str the string to find, null returns -1
+ * @return the last index of the string, or -1 if not found
+ */
+ public int lastIndexOf(final String str) {
+ return lastIndexOf(str, size - 1);
+ }
+
+ /**
+ * Searches the string builder to find the last reference to the specified
+ * string starting searching from the given index.
+ * <p>
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * </p>
+ *
+ * @param str the string to find, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the last index of the string, or -1 if not found
+ */
+ public int lastIndexOf(final String str, final int startIndex) {
+ return StringUtils.lastIndexOf(this, str, startIndex);
+ }
+
+ /**
+ * Searches the string builder using the matcher to find the last match.
+ * <p>
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ * </p>
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @return the last index matched, or -1 if not found
+ */
+ public int lastIndexOf(final StrMatcher matcher) {
+ return lastIndexOf(matcher, size);
+ }
+
+ /**
+ * Searches the string builder using the matcher to find the last
+ * match searching from the given index.
+ * <p>
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ * </p>
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return the last index matched, or -1 if not found
+ */
+ public int lastIndexOf(final StrMatcher matcher, int startIndex) {
+ startIndex = startIndex >= size ? size - 1 : startIndex;
+ if (matcher == null || startIndex < 0) {
+ return -1;
+ }
+ final char[] buf = buffer;
+ final int endIndex = startIndex + 1;
+ for (int i = startIndex; i >= 0; i--) {
+ if (matcher.isMatch(buf, i, 0, endIndex) > 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Creates a tokenizer that can tokenize the contents of this builder.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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:
+ * </p>
+ * <pre>
+ * 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
+ * </pre>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])}
+ * with a non-null value will break the link with the builder.
+ * </p>
+ *
+ * @return a tokenizer that is linked to this builder
+ */
+ public StrTokenizer asTokenizer() {
+ return new StrBuilderTokenizer();
+ }
+
+ /**
+ * Gets the contents of this builder as a Reader.
+ * <p>
+ * This method allows the contents of the builder to be read
+ * using any standard method that expects a Reader.
+ * </p>
+ * <p>
+ * To use, simply create a {@link StrBuilder}, populate it with
+ * data, call {@code asReader}, and then read away.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * The returned reader supports marking, and ignores the flush method.
+ * </p>
+ *
+ * @return a reader that reads from this builder
+ */
+ public Reader asReader() {
+ return new StrBuilderReader();
+ }
+
+ /**
+ * Gets this builder as a Writer that can be written to.
+ * <p>
+ * This method allows you to populate the contents of the builder
+ * using any standard method that takes a Writer.
+ * </p>
+ * <p>
+ * 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}.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * The returned writer ignores the close and flush methods.
+ * </p>
+ *
+ * @return a writer that populates this builder
+ */
+ public Writer asWriter() {
+ return new StrBuilderWriter();
+ }
+
+ /**
+ * Appends current contents of this {@link StrBuilder} to the
+ * provided {@link Appendable}.
+ * <p>
+ * This method tries to avoid doing any extra copies of contents.
+ * </p>
+ *
+ * @param appendable the appendable to append data to
+ * @throws IOException if an I/O error occurs
+ *
+ * @since 3.4
+ * @see #readFrom(Readable)
+ */
+ 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 {
+ 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;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * @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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Gets a String version of the string builder, creating a new instance
+ * each time the method is called.
+ * <p>
+ * Note that unlike StringBuffer, the string version returned is
+ * independent of the string builder.
+ * </p>
+ *
+ * @return the builder as a String
+ */
+ @Override
+ public String toString() {
+ return new String(buffer, 0, size);
+ }
+
+ /**
+ * Gets a StringBuffer version of the string builder, creating a
+ * new instance each time the method is called.
+ *
+ * @return the builder as a StringBuffer
+ */
+ public StringBuffer toStringBuffer() {
+ return new StringBuffer(size).append(buffer, 0, size);
+ }
+
+ /**
+ * Gets a StringBuilder version of the string builder, creating a
+ * new instance each time the method is called.
+ *
+ * @return the builder as a StringBuilder
+ * @since 3.2
+ */
+ public StringBuilder toStringBuilder() {
+ return new StringBuilder(size).append(buffer, 0, size);
+ }
+
+ /**
+ * Implement the {@link Builder} interface.
+ * @return the builder as a String
+ * @since 3.2
+ * @see #toString()
+ */
+ @Override
+ public String build() {
+ return toString();
+ }
+
+ /**
+ * Validates parameters defining a range of the builder.
+ *
+ * @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
+ */
+ protected int validateRange(final int startIndex, int endIndex) {
+ if (startIndex < 0) {
+ throw new StringIndexOutOfBoundsException(startIndex);
+ }
+ if (endIndex > size) {
+ endIndex = size;
+ }
+ if (startIndex > endIndex) {
+ throw new StringIndexOutOfBoundsException("end < start");
+ }
+ 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<String> 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
new file mode 100644
index 000000000..52ef035e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/StrLookup.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.SystemProperties;
+
+/**
+ * Lookup a String key to a String value.
+ * <p>
+ * This class represents the simplest form of a string to string map.
+ * It has a benefit over a map in that it can create the result on
+ * demand based on the key.
+ * </p>
+ * <p>
+ * This class comes complete with various factory methods.
+ * If these do not suffice, you can subclass and implement your own matcher.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @param <V> Unused.
+ * @since 2.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/lookup/StringLookupFactory.html">
+ * StringLookupFactory</a> instead
+ */
+@Deprecated
+public abstract class StrLookup<V> {
+
+ /**
+ * Lookup that always returns null.
+ */
+ private static final StrLookup<String> NONE_LOOKUP = new MapStrLookup<>(null);
+
+ /**
+ * Lookup based on system properties.
+ */
+ private static final StrLookup<String> 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}.
+ * <p>
+ * If a security manager blocked access to system properties, then null will
+ * be returned from every lookup.
+ * </p>
+ * <p>
+ * If a null key is used, this lookup will throw a NullPointerException.
+ * </p>
+ *
+ * @return a lookup using system properties, not null
+ */
+ public static StrLookup<String> systemPropertiesLookup() {
+ return SYSTEM_PROPERTIES_LOOKUP;
+ }
+
+ /**
+ * Returns a lookup which looks up values using a map.
+ * <p>
+ * If the map is null, then null will be returned from every lookup.
+ * The map result object is converted to a string using toString().
+ * </p>
+ *
+ * @param <V> 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 <V> StrLookup<V> mapLookup(final Map<String, V> map) {
+ return new MapStrLookup<>(map);
+ }
+
+ /**
+ * Constructor.
+ */
+ protected StrLookup() {
+ }
+
+ /**
+ * Looks up a String key to a String value.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * The {@link #lookup(String)} method always returns a String, regardless of
+ * the underlying data, by converting it as necessary. For example:
+ * </p>
+ * <pre>
+ * Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
+ * map.put("number", Integer.valueOf(2));
+ * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+ * </pre>
+ * @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.
+ *
+ * @param <V> the type of mapped values.
+ */
+ static class MapStrLookup<V> extends StrLookup<V> {
+
+ /** Map keys are variable names and value. */
+ private final Map<String, V> map;
+
+ /**
+ * Creates a new instance backed by a Map.
+ *
+ * @param map the map of keys to values, may be null
+ */
+ MapStrLookup(final Map<String, V> map) {
+ this.map = map;
+ }
+
+ /**
+ * Looks up a String key to a String value using the map.
+ * <p>
+ * If the map is null, then null is returned.
+ * The map result object is converted to a string using toString().
+ * </p>
+ *
+ * @param key the key to be looked up, may be null
+ * @return the matching value, null if no match
+ */
+ @Override
+ public String lookup(final String key) {
+ if (map == null) {
+ return null;
+ }
+ final Object obj = map.get(key);
+ if (obj == null) {
+ return null;
+ }
+ return obj.toString();
+ }
+ }
+
+ /**
+ * Lookup implementation based on system properties.
+ */
+ private static class SystemPropertiesStrLookup extends StrLookup<String> {
+ /**
+ * {@inheritDoc} This implementation directly accesses system properties.
+ */
+ @Override
+ public String lookup(final String key) {
+ return SystemProperties.getProperty(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
new file mode 100644
index 000000000..b29d4fcf4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/StrMatcher.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang3.ArraySorter;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A matcher class that can be queried to determine if a character array
+ * portion matches.
+ * <p>
+ * This class comes complete with various factory methods.
+ * If these do not suffice, you can subclass and implement your own matcher.
+ * </p>
+ *
+ * @since 2.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/matcher/StringMatcherFactory.html">
+ * StringMatcherFactory</a> instead
+ */
+@Deprecated
+public abstract class StrMatcher {
+
+ /**
+ * 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();
+
+ /**
+ * 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;
+ }
+ 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());
+ }
+
+ /**
+ * 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.
+ * <p>
+ * 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}.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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}.
+ * </p>
+ * <p>
+ * The matching code may check one character or many.
+ * It may check characters preceding {@code pos} as well as those after.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ */
+ static final class CharSetMatcher extends StrMatcher {
+ /** The set of characters to match. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a character array.
+ *
+ * @param chars the characters to match, must not be null
+ */
+ CharSetMatcher(final char[] chars) {
+ this.chars = ArraySorter.sort(chars.clone());
+ }
+
+ /**
+ * 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 Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
+ }
+ }
+
+ /**
+ * Class used to define a character for matching purposes.
+ */
+ static final class CharMatcher extends StrMatcher {
+ /** The character to match. */
+ private final char ch;
+
+ /**
+ * Constructor that creates a matcher that matches a single character.
+ *
+ * @param ch the character to match
+ */
+ CharMatcher(final char ch) {
+ this.ch = ch;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Class used to define a set of characters for matching purposes.
+ */
+ static final class StringMatcher extends StrMatcher {
+ /** The string to match, as a character array. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a String.
+ *
+ * @param str the string to match, must not be null
+ */
+ StringMatcher(final String str) {
+ chars = str.toCharArray();
+ }
+
+ /**
+ * Returns whether or not the given text matches the stored string.
+ *
+ * @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, int pos, final int bufferStart, final int bufferEnd) {
+ final int len = chars.length;
+ if (pos + len > bufferEnd) {
+ return 0;
+ }
+ for (int i = 0; i < chars.length; i++, pos++) {
+ if (chars[i] != buffer[pos]) {
+ return 0;
+ }
+ }
+ return len;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ' ' + Arrays.toString(chars);
+ }
+
+ }
+
+ /**
+ * 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().
+ */
+ static final class TrimMatcher extends StrMatcher {
+
+ /**
+ * Constructs a new instance of {@link TrimMatcher}.
+ */
+ TrimMatcher() {
+ }
+
+ /**
+ * 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 buffer[pos] <= 32 ? 1 : 0;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
new file mode 100644
index 000000000..191ed0d5c
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
@@ -0,0 +1,1245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Substitutes variables within a string by values.
+ * <p>
+ * This class takes a piece of text and substitutes all the variables within it.
+ * The default definition of a variable is {@code ${variableName}}.
+ * The prefix and suffix can be changed via constructors and set methods.
+ * </p>
+ * <p>
+ * Variable values are typically resolved from a map, but could also be resolved
+ * from system properties, or by supplying a custom variable resolver.
+ * </p>
+ * <p>
+ * The simplest example is to use this class to replace Java System properties. For example:
+ * </p>
+ * <pre>
+ * StrSubstitutor.replaceSystemProperties(
+ * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ * </pre>
+ * <p>
+ * Typical usage of this class follows the following pattern: First an instance is created
+ * and initialized with the map that contains the values for the available variables.
+ * If a prefix and/or suffix for variables should be used other than the default ones,
+ * the appropriate settings can be performed. After that the {@code replace()}
+ * method can be called passing in the source text for interpolation. In the returned
+ * text all variable references (as long as their values are known) will be resolved.
+ * The following example demonstrates this:
+ * </p>
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
+ * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
+ * String templateString = &quot;The ${animal} jumps over the ${target}.&quot;;
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * The quick brown fox jumps over the lazy dog.
+ * </pre>
+ * <p>
+ * Also, this class allows to set a default value for unresolved variables.
+ * The default value for a variable can be appended to the variable name after the variable
+ * default value delimiter. The default value of the variable default value delimiter is ':-',
+ * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
+ * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
+ * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
+ * The following shows an example with variable default value settings:
+ * </p>
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
+ * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
+ * String templateString = &quot;The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.&quot;;
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * <p>
+ * yielding:
+ * </p>
+ * <pre>
+ * The quick brown fox jumps over the lazy dog. 1234567890.
+ * </pre>
+ * <p>
+ * In addition to this usage pattern there are some static convenience methods that
+ * cover the most common use cases. These methods can be used without the need of
+ * manually creating an instance. However if multiple replace operations are to be
+ * performed, creating and reusing an instance of this class will be more efficient.
+ * </p>
+ * <p>
+ * Variable replacement works in a recursive way. Thus, if a variable value contains
+ * a variable then that variable will also be replaced. Cyclic replacements are
+ * detected and will cause an exception to be thrown.
+ * </p>
+ * <p>
+ * Sometimes the interpolation's result must contain a variable prefix. As an example
+ * take the following source text:
+ * </p>
+ * <pre>
+ * The variable ${${name}} must be used.
+ * </pre>
+ * <p>
+ * Here only the variable's name referred to in the text should be replaced resulting
+ * in the text (assuming that the value of the {@code name} variable is {@code x}):
+ * </p>
+ * <pre>
+ * The variable ${x} must be used.
+ * </pre>
+ * <p>
+ * To achieve this effect there are two possibilities: Either set a different prefix
+ * and suffix for variables which do not conflict with the result text you want to
+ * produce. The other possibility is to use the escape character, by default '$'.
+ * If this character is placed before a variable reference, this reference is ignored
+ * and won't be replaced. For example:
+ * </p>
+ * <pre>
+ * The variable $${${name}} must be used.
+ * </pre>
+ * <p>
+ * In some complex scenarios you might even want to perform substitution in the
+ * names of variables, for instance:
+ * </p>
+ * <pre>
+ * ${jre-${java.specification.version}}
+ * </pre>
+ * <p>
+ * {@link StrSubstitutor} supports this recursive substitution in variable
+ * names, but it has to be enabled explicitly by setting the
+ * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
+ * property to <b>true</b>.
+ * </p>
+ * <p>
+ * This class is <b>not</b> thread safe.
+ * </p>
+ *
+ * @since 2.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringSubstitutor.html">
+ * StringSubstitutor</a> instead
+ */
+@Deprecated
+public class StrSubstitutor {
+
+ /**
+ * Constant for the default escape character.
+ */
+ public static final char DEFAULT_ESCAPE = '$';
+ /**
+ * Constant for the default variable prefix.
+ */
+ public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
+ /**
+ * Constant for the default variable suffix.
+ */
+ public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
+ /**
+ * Constant for the default value delimiter of a variable.
+ * @since 3.2
+ */
+ 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.
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @return the result of the replace operation
+ */
+ public static <V> String replace(final Object source, final Map<String, V> 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
+ * custom variable prefix and suffix
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @param prefix the prefix of variables, not null
+ * @param suffix the suffix of variables, not null
+ * @return the result of the replace operation
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public static <V> String replace(final Object source, final Map<String, V> 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.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueProperties the properties with values, may be null
+ * @return the result of the replace operation
+ */
+ public static String replace(final Object source, final Properties valueProperties) {
+ if (valueProperties == null) {
+ return source.toString();
+ }
+ final Map<String, String> valueMap = new HashMap<>();
+ final Enumeration<?> propNames = valueProperties.propertyNames();
+ while (propNames.hasMoreElements()) {
+ final String propName = String.valueOf(propNames.nextElement());
+ final String propValue = valueProperties.getProperty(propName);
+ valueMap.put(propName, propValue);
+ }
+ return replace(source, valueMap);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the system properties.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @return the result of the replace operation
+ */
+ public static String replaceSystemProperties(final Object source) {
+ return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
+ }
+
+ /**
+ * Creates a new instance with defaults for variable prefix and suffix
+ * and the escaping character.
+ */
+ public StrSubstitutor() {
+ this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses defaults for variable
+ * prefix and suffix and the escaping character.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap) {
+ this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses a default escaping character.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver) {
+ this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
+ final char escape) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter string, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiter(valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(
+ final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
+ final char escape) {
+ this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public StrSubstitutor(
+ final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
+ final char escape, final StrMatcher valueDelimiterMatcher) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefixMatcher(prefixMatcher);
+ this.setVariableSuffixMatcher(suffixMatcher);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source string as a template.
+ *
+ * @param source the string to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final String source) {
+ if (source == null) {
+ return null;
+ }
+ 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.
+ * <p>
+ * Only the specified portion of the string will be processed.
+ * The rest of the string is not processed, and is not returned.
+ * </p>
+ *
+ * @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 array as a template.
+ * The array is not altered by this method.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length).append(source);
+ substitute(buf, 0, source.length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source array as a template.
+ * The array is not altered by this method.
+ * <p>
+ * Only the specified portion of the array will be processed.
+ * The rest of the array is not processed, and is not returned.
+ * </p>
+ *
+ * @param source the character array to replace in, not altered, 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 char[] 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 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.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ * </p>
+ *
+ * @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.
+ * The source 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
+ * @since 3.2
+ */
+ public String replace(final CharSequence source) {
+ if (source == null) {
+ return null;
+ }
+ return replace(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source as a template.
+ * The source is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ * </p>
+ *
+ * @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
+ * @since 3.2
+ */
+ public String replace(final CharSequence 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 builder as a template.
+ * The builder is not altered by this method.
+ *
+ * @param source the builder to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder 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 builder as a template.
+ * The builder is not altered by this method.
+ * <p>
+ * Only the specified portion of the builder will be processed.
+ * The rest of the builder is not processed, and is not returned.
+ * </p>
+ *
+ * @param source the builder 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 StrBuilder 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 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 within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, but it is not deleted.
+ * </p>
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the buffer to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ * @since 3.2
+ */
+ public boolean replaceIn(final StringBuilder source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source builder
+ * with their matching values from the resolver.
+ * The builder is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, but it is not deleted.
+ * </p>
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the buffer to be processed, must be valid
+ * @return true if altered
+ * @since 3.2
+ */
+ public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ /**
+ * 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.
+ * <p>
+ * Only the specified portion of the builder will be processed.
+ * The rest of the builder is not processed, but it is not deleted.
+ * </p>
+ *
+ * @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);
+ }
+
+ /**
+ * Internal method that substitutes the variables.
+ * <p>
+ * Most users of this class do not need to call this method. This method will
+ * be called automatically by another (public) method.
+ * </p>
+ * <p>
+ * Writers of subclasses can override this method if they need access to
+ * the substitution process at the start or end.
+ * </p>
+ *
+ * @param buf the string builder to substitute into, not null
+ * @param offset the start offset within the builder, must be valid
+ * @param length the length within the builder to be processed, must be valid
+ * @return true if altered
+ */
+ protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
+ return substitute(buf, offset, length, null) > 0;
+ }
+
+ /**
+ * Recursive handler for multiple levels of interpolation. This is the main
+ * interpolation method, which resolves the values of all variable references
+ * contained in the passed-in text.
+ *
+ * @param buf the string builder to substitute into, not null
+ * @param offset the start offset within the builder, must be valid
+ * @param length the length within the builder to be processed, must be valid
+ * @param priorVariables the stack keeping track of the replaced variables, may be null
+ * @return the length change that occurs, unless priorVariables is null when the int
+ * represents a boolean flag as to whether any change occurred.
+ */
+ private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
+ final StrMatcher pfxMatcher = getVariablePrefixMatcher();
+ final StrMatcher suffMatcher = getVariableSuffixMatcher();
+ final char escape = getEscapeChar();
+ final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
+ final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
+
+ final boolean top = priorVariables == null;
+ boolean altered = false;
+ int lengthChange = 0;
+ char[] chars = buf.buffer;
+ int bufEnd = offset + length;
+ int pos = offset;
+ while (pos < bufEnd) {
+ final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
+ bufEnd);
+ if (startMatchLen == 0) {
+ pos++;
+ } else // found variable start marker
+ if (pos > offset && chars[pos - 1] == escape) {
+ // escaped
+ if (preserveEscapes) {
+ pos++;
+ continue;
+ }
+ buf.deleteCharAt(pos - 1);
+ chars = buf.buffer; // in case buffer was altered
+ lengthChange--;
+ altered = true;
+ bufEnd--;
+ } else {
+ // find suffix
+ final int startPos = pos;
+ pos += startMatchLen;
+ int endMatchLen;
+ int nestedVarCount = 0;
+ while (pos < bufEnd) {
+ if (substitutionInVariablesEnabled
+ && (endMatchLen = pfxMatcher.isMatch(chars,
+ pos, offset, bufEnd)) != 0) {
+ // found a nested variable start
+ nestedVarCount++;
+ pos += endMatchLen;
+ continue;
+ }
+
+ endMatchLen = suffMatcher.isMatch(chars, pos, offset,
+ bufEnd);
+ if (endMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable end marker
+ if (nestedVarCount == 0) {
+ String varNameExpr = new String(chars, startPos
+ + startMatchLen, pos - startPos
+ - startMatchLen);
+ if (substitutionInVariablesEnabled) {
+ final StrBuilder bufName = new StrBuilder(varNameExpr);
+ substitute(bufName, 0, bufName.length());
+ varNameExpr = bufName.toString();
+ }
+ pos += endMatchLen;
+ final int endPos = pos;
+
+ String varName = varNameExpr;
+ String varDefaultValue = null;
+
+ if (valueDelimMatcher != null) {
+ final char [] varNameExprChars = varNameExpr.toCharArray();
+ int valueDelimiterMatchLen;
+ for (int i = 0; i < varNameExprChars.length; i++) {
+ // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
+ if (!substitutionInVariablesEnabled
+ && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
+ break;
+ }
+ if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ }
+
+ // on the first call initialize priorVariables
+ if (priorVariables == null) {
+ priorVariables = new ArrayList<>();
+ priorVariables.add(new String(chars,
+ offset, length));
+ }
+
+ // handle cyclic substitution
+ checkCyclicSubstitution(varName, priorVariables);
+ priorVariables.add(varName);
+
+ // resolve the variable
+ String varValue = resolveVariable(varName, buf,
+ startPos, endPos);
+ if (varValue == null) {
+ varValue = varDefaultValue;
+ }
+ if (varValue != null) {
+ // recursive replace
+ final int varLen = varValue.length();
+ buf.replace(startPos, endPos, varValue);
+ altered = true;
+ int change = substitute(buf, startPos,
+ varLen, priorVariables);
+ change = change
+ + varLen - (endPos - startPos);
+ pos += change;
+ bufEnd += change;
+ lengthChange += change;
+ chars = buf.buffer; // in case buffer was
+ // altered
+ }
+
+ // remove variable from the cyclic stack
+ priorVariables
+ .remove(priorVariables.size() - 1);
+ break;
+ }
+ nestedVarCount--;
+ pos += endMatchLen;
+ }
+ }
+ }
+ }
+ if (top) {
+ return altered ? 1 : 0;
+ }
+ 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<String> 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.
+ * <p>
+ * Most users of this class do not need to call this method. This method is
+ * called automatically by the substitution process.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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 <b>null</b> 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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @return the prefix matcher in use
+ */
+ public StrMatcher getVariablePrefixMatcher() {
+ return prefixMatcher;
+ }
+
+ /**
+ * Sets the variable prefix matcher currently in use.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @return the suffix matcher in use
+ */
+ public StrMatcher getVariableSuffixMatcher() {
+ return suffixMatcher;
+ }
+
+ /**
+ * Sets the variable suffix matcher currently in use.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * If it returns null, then the variable default value resolution is disabled.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
+ * becomes disabled.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * If the {@code valueDelimiter} is null or empty string, then the variable default
+ * value resolution becomes disabled.
+ * </p>
+ *
+ * @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
+ * <b>true</b>, 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 <b>false</b>.
+ *
+ * @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 <b>true</b>, the escape character is retained
+ * during substitution (e.g. {@code $${this-is-escaped}} remains
+ * {@code $${this-is-escaped}}). If set to <b>false</b>, the escape
+ * character is removed during substitution (e.g.
+ * {@code $${this-is-escaped}} becomes
+ * {@code ${this-is-escaped}}). The default value is <b>false</b>
+ *
+ * @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
new file mode 100644
index 000000000..3236329a6
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/StrTokenizer.java
@@ -0,0 +1,1109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Tokenizes a string based on delimiters (separators)
+ * and supporting quoting and ignored character concepts.
+ * <p>
+ * This class can split a String into many smaller strings. It aims
+ * to do a similar job to {@link java.util.StringTokenizer StringTokenizer},
+ * however it offers much more control and flexibility including implementing
+ * the {@link ListIterator} interface. By default, it is set up
+ * like {@link StringTokenizer}.
+ * </p>
+ * <p>
+ * The input String is split into a number of <i>tokens</i>.
+ * Each token is separated from the next String by a <i>delimiter</i>.
+ * One or more delimiter characters must be specified.
+ * </p>
+ * <p>
+ * Each token may be surrounded by quotes.
+ * The <i>quote</i> matcher specifies the quote character(s).
+ * A quote may be escaped within a quoted section by duplicating itself.
+ * </p>
+ * <p>
+ * Between each token and the delimiter are potentially characters that need trimming.
+ * The <i>trimmer</i> matcher specifies these characters.
+ * One usage might be to trim whitespace characters.
+ * </p>
+ * <p>
+ * At any point outside the quotes there might potentially be invalid characters.
+ * The <i>ignored</i> matcher specifies these characters to be removed.
+ * One usage might be to remove new line characters.
+ * </p>
+ * <p>
+ * Empty tokens may be removed or returned as null.
+ * </p>
+ * <pre>
+ * "a,b,c" - Three tokens "a","b","c" (comma delimiter)
+ * " a, b , c " - Three tokens "a","b","c" (default CSV processing trims whitespace)
+ * "a, ", b ,", c" - Three tokens "a, " , " b ", ", c" (quoted text untouched)
+ * </pre>
+ *
+ * <table>
+ * <caption>StrTokenizer properties and options</caption>
+ * <tr>
+ * <th>Property</th><th>Type</th><th>Default</th>
+ * </tr>
+ * <tr>
+ * <td>delim</td><td>CharSetMatcher</td><td>{ \t\n\r\f}</td>
+ * </tr>
+ * <tr>
+ * <td>quote</td><td>NoneMatcher</td><td>{}</td>
+ * </tr>
+ * <tr>
+ * <td>ignore</td><td>NoneMatcher</td><td>{}</td>
+ * </tr>
+ * <tr>
+ * <td>emptyTokenAsNull</td><td>boolean</td><td>false</td>
+ * </tr>
+ * <tr>
+ * <td>ignoreEmptyTokens</td><td>boolean</td><td>true</td>
+ * </tr>
+ * </table>
+ *
+ * @since 2.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringTokenizer.html">
+ * StringTokenizer</a> instead
+ */
+@Deprecated
+public class StrTokenizer implements ListIterator<String>, Cloneable {
+
+ private static final StrTokenizer CSV_TOKENIZER_PROTOTYPE;
+ private static final StrTokenizer TSV_TOKENIZER_PROTOTYPE;
+ static {
+ CSV_TOKENIZER_PROTOTYPE = new StrTokenizer();
+ CSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.commaMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false);
+ CSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false);
+
+ TSV_TOKENIZER_PROTOTYPE = new StrTokenizer();
+ TSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.tabMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false);
+ 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}.
+ *
+ * @return a clone of {@code CSV_TOKENIZER_PROTOTYPE}.
+ */
+ 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
+ * will be trim whitespace from both ends (which can be overridden with
+ * the setTrimmer method).
+ * <p>
+ * You must call a "reset" method to set the string which you want to parse.
+ * </p>
+ * @return a new tokenizer instance which parses Comma Separated Value strings
+ */
+ 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
+ * 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 char[] input) {
+ final StrTokenizer tok = getCSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ /**
+ * Returns a clone of {@code TSV_TOKENIZER_PROTOTYPE}.
+ *
+ * @return a clone of {@code TSV_TOKENIZER_PROTOTYPE}.
+ */
+ 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
+ * (which can be overridden with the setTrimmer method).
+ * <p>
+ * You must call a "reset" method to set the string which you want to parse.
+ * </p>
+ * @return a new tokenizer instance which parses Tab Separated Value strings.
+ */
+ 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
+ * (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 char[] input) {
+ final StrTokenizer tok = getTSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ /**
+ * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * as per StringTokenizer, but with no text to tokenize.
+ * <p>
+ * This constructor is normally used with {@link #reset(String)}.
+ * </p>
+ */
+ public StrTokenizer() {
+ 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
+ */
+ public StrTokenizer(final String input) {
+ if (input != null) {
+ chars = input.toCharArray();
+ } else {
+ chars = null;
+ }
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter character.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter character
+ */
+ public StrTokenizer(final String input, final char delim) {
+ this(input);
+ setDelimiterChar(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter string.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter string
+ */
+ public StrTokenizer(final String 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
+ * @param delim the field delimiter matcher
+ */
+ public StrTokenizer(final String 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
+ * @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.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter matcher
+ * @param quote the field quoted string matcher
+ */
+ public StrTokenizer(final String 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.
+ *
+ * @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 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++];
+ }
+ return null;
+ }
+
+ /**
+ * Gets the previous token from the String.
+ *
+ * @return the previous sequential token, or null when no more tokens are found
+ */
+ public String previousToken() {
+ if (hasPrevious()) {
+ return tokens[--tokenPos];
+ }
+ return null;
+ }
+
+ /**
+ * Gets a copy of the full token list as an independent modifiable array.
+ *
+ * @return the tokens as a String array
+ */
+ public String[] getTokenArray() {
+ checkTokenized();
+ return tokens.clone();
+ }
+
+ /**
+ * Gets a copy of the full token list as an independent modifiable list.
+ *
+ * @return the tokens as a String array
+ */
+ public List<String> getTokenList() {
+ checkTokenized();
+ final List<String> list = new ArrayList<>(tokens.length);
+ list.addAll(Arrays.asList(tokens));
+ return list;
+ }
+
+ /**
+ * Resets this tokenizer, forgetting all parsing and iteration already completed.
+ * <p>
+ * This method allows the same tokenizer to be reused for the same String.
+ * </p>
+ *
+ * @return this, to enable chaining
+ */
+ 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;
+ }
+
+ /**
+ * Checks whether there are any more tokens.
+ *
+ * @return true if there are more tokens
+ */
+ @Override
+ public boolean hasNext() {
+ checkTokenized();
+ return tokenPos < tokens.length;
+ }
+
+ /**
+ * Gets the next token.
+ *
+ * @return the next String token
+ * @throws NoSuchElementException if there are no more elements
+ */
+ @Override
+ public String next() {
+ if (hasNext()) {
+ return tokens[tokenPos++];
+ }
+ throw new NoSuchElementException();
+ }
+
+ /**
+ * Gets the index of the next token to return.
+ *
+ * @return the next token index
+ */
+ @Override
+ public int nextIndex() {
+ return tokenPos;
+ }
+
+ /**
+ * 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 the token previous to the last returned token.
+ *
+ * @return the previous token
+ */
+ @Override
+ public String previous() {
+ if (hasPrevious()) {
+ return tokens[--tokenPos];
+ }
+ throw new NoSuchElementException();
+ }
+
+ /**
+ * Gets the index of the previous token.
+ *
+ * @return the previous token index
+ */
+ @Override
+ public int previousIndex() {
+ return tokenPos - 1;
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @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<String> split = tokenize(null, 0, 0);
+ tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ } else {
+ final List<String> split = tokenize(chars, 0, chars.length);
+ tokens = split.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
+ }
+ }
+ }
+
+ /**
+ * Internal method to performs the tokenization.
+ * <p>
+ * Most users of this class do not need to call this method. This method
+ * will be called automatically by other (public) methods when required.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * {@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.
+ * </p>
+ *
+ * @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<String> tokenize(final char[] srcChars, final int offset, final int count) {
+ if (ArrayUtils.isEmpty(srcChars)) {
+ return Collections.emptyList();
+ }
+ final StrBuilder buf = new StrBuilder();
+ final List<String> 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<String> list, String tok) {
+ if (StringUtils.isEmpty(tok)) {
+ if (isIgnoreEmptyTokens()) {
+ return;
+ }
+ if (isEmptyTokenAsNull()) {
+ tok = null;
+ }
+ }
+ list.add(tok);
+ }
+
+ /**
+ * Reads character by character through the String to get the next token.
+ *
+ * @param srcChars the character array being tokenized
+ * @param start the first character of field
+ * @param len the length of the character array being tokenized
+ * @param workArea a temporary work area
+ * @param tokenList the list of parsed tokens
+ * @return the starting position of the next field (the character
+ * immediately after the delimiter), or -1 if end of string found
+ */
+ private int readNextToken(final char[] srcChars, int start, final int len, final StrBuilder workArea, final List<String> tokenList) {
+ // skip all leading whitespace, unless it is the
+ // field delimiter or the quote character
+ while (start < len) {
+ final int removeLen = Math.max(
+ getIgnoredMatcher().isMatch(srcChars, start, start, len),
+ getTrimmerMatcher().isMatch(srcChars, start, start, len));
+ if (removeLen == 0 ||
+ getDelimiterMatcher().isMatch(srcChars, start, start, len) > 0 ||
+ getQuoteMatcher().isMatch(srcChars, start, start, len) > 0) {
+ break;
+ }
+ start += removeLen;
+ }
+
+ // handle reaching end
+ if (start >= len) {
+ addToken(tokenList, StringUtils.EMPTY);
+ return -1;
+ }
+
+ // handle empty token
+ final int delimLen = getDelimiterMatcher().isMatch(srcChars, start, start, len);
+ if (delimLen > 0) {
+ addToken(tokenList, StringUtils.EMPTY);
+ return start + delimLen;
+ }
+
+ // handle found token
+ final int quoteLen = getQuoteMatcher().isMatch(srcChars, start, start, len);
+ if (quoteLen > 0) {
+ return readWithQuotes(srcChars, start + quoteLen, len, workArea, tokenList, start, quoteLen);
+ }
+ return readWithQuotes(srcChars, start, len, workArea, tokenList, 0, 0);
+ }
+
+ /**
+ * Reads a possibly quoted string token.
+ *
+ * @param srcChars the character array being tokenized
+ * @param start the first character of field
+ * @param len the length of the character array being tokenized
+ * @param workArea a temporary work area
+ * @param tokenList the list of parsed tokens
+ * @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 the starting position of the next field (the character
+ * immediately after the delimiter, or if end of string found,
+ * then the length of string
+ */
+ private int readWithQuotes(final char[] srcChars, final int start, final int len, final StrBuilder workArea,
+ final List<String> tokenList, final int quoteStart, final int quoteLen) {
+ // Loop until we've found the end of the quoted
+ // string or the end of the input
+ workArea.clear();
+ int pos = start;
+ boolean quoting = quoteLen > 0;
+ int trimStart = 0;
+
+ while (pos < len) {
+ // quoting mode can occur several times throughout a string
+ // we must switch between quoting and non-quoting until we
+ // encounter a non-quoted delimiter, or end of string
+ if (quoting) {
+ // In quoting mode
+
+ // If we've found a quote character, see if it's
+ // followed by a second quote. If so, then we need
+ // to actually put the quote character into the token
+ // rather than end the token.
+ if (isQuote(srcChars, pos, len, quoteStart, quoteLen)) {
+ if (isQuote(srcChars, pos + quoteLen, len, quoteStart, quoteLen)) {
+ // matched pair of quotes, thus an escaped quote
+ workArea.append(srcChars, pos, quoteLen);
+ pos += quoteLen * 2;
+ trimStart = workArea.size();
+ continue;
+ }
+
+ // end of quoting
+ quoting = false;
+ pos += quoteLen;
+ continue;
+ }
+
+ } else {
+ // Not in quoting mode
+
+ // check for delimiter, and thus end of token
+ final int delimLen = getDelimiterMatcher().isMatch(srcChars, pos, start, len);
+ if (delimLen > 0) {
+ // return condition when end of token found
+ addToken(tokenList, workArea.substring(0, trimStart));
+ return pos + delimLen;
+ }
+
+ // check for quote, and thus back into quoting mode
+ if (quoteLen > 0 && isQuote(srcChars, pos, len, quoteStart, quoteLen)) {
+ quoting = true;
+ pos += quoteLen;
+ continue;
+ }
+
+ // check for ignored (outside quotes), and ignore
+ final int ignoredLen = getIgnoredMatcher().isMatch(srcChars, pos, start, len);
+ if (ignoredLen > 0) {
+ pos += ignoredLen;
+ continue;
+ }
+
+ // check for trimmed character
+ // don't yet know if it's at the end, so copy to workArea
+ // use trimStart to keep track of trim at the end
+ final int trimmedLen = getTrimmerMatcher().isMatch(srcChars, pos, start, len);
+ if (trimmedLen > 0) {
+ workArea.append(srcChars, pos, trimmedLen);
+ pos += trimmedLen;
+ continue;
+ }
+ }
+ // copy regular character from inside quotes
+ workArea.append(srcChars[pos++]);
+ trimStart = workArea.size();
+ }
+
+ // return condition when end of string found
+ addToken(tokenList, workArea.substring(0, trimStart));
+ return -1;
+ }
+
+ /**
+ * 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 field delimiter matcher.
+ *
+ * @return the delimiter matcher in use
+ */
+ public StrMatcher getDelimiterMatcher() {
+ return this.delimMatcher;
+ }
+
+ /**
+ * Sets the field delimiter matcher.
+ * <p>
+ * The delimiter is used to separate one token from another.
+ * </p>
+ *
+ * @param delim the delimiter matcher to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setDelimiterMatcher(final StrMatcher delim) {
+ if (delim == null) {
+ this.delimMatcher = StrMatcher.noneMatcher();
+ } else {
+ this.delimMatcher = delim;
+ }
+ 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.
+ *
+ * @param delim the delimiter string to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setDelimiterString(final String delim) {
+ return setDelimiterMatcher(StrMatcher.stringMatcher(delim));
+ }
+
+ /**
+ * Gets the quote matcher currently in use.
+ * <p>
+ * 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).
+ * </p>
+ *
+ * @return the quote matcher in use
+ */
+ public StrMatcher getQuoteMatcher() {
+ return quoteMatcher;
+ }
+
+ /**
+ * Set the quote matcher to use.
+ * <p>
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ * </p>
+ *
+ * @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;
+ }
+
+ /**
+ * Sets the quote character to use.
+ * <p>
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ * </p>
+ *
+ * @param quote the quote 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.
+ * <p>
+ * These characters are ignored when parsing the String, unless they are
+ * within a quoted region.
+ * The default value is not to ignore anything.
+ * </p>
+ *
+ * @return the ignored matcher in use
+ */
+ public StrMatcher getIgnoredMatcher() {
+ return ignoredMatcher;
+ }
+
+ /**
+ * Set the matcher for characters to ignore.
+ * <p>
+ * These characters are ignored when parsing the String, unless they are
+ * within a quoted region.
+ * </p>
+ *
+ * @param ignored the ignored matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoredMatcher(final StrMatcher ignored) {
+ if (ignored != null) {
+ this.ignoredMatcher = ignored;
+ }
+ return this;
+ }
+
+ /**
+ * Set the character to ignore.
+ * <p>
+ * This character is ignored when parsing the String, unless it is
+ * within a quoted region.
+ *
+ * @param ignored the ignored character to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoredChar(final char ignored) {
+ return setIgnoredMatcher(StrMatcher.charMatcher(ignored));
+ }
+
+ /**
+ * Gets the trimmer character matcher.
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @return the trimmer matcher in use
+ */
+ public StrMatcher getTrimmerMatcher() {
+ return trimmerMatcher;
+ }
+
+ /**
+ * Sets the matcher for characters to trim.
+ * <p>
+ * These characters are trimmed off on each side of the delimiter
+ * until the token or quote is found.
+ * </p>
+ *
+ * @param trimmer the trimmer matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setTrimmerMatcher(final StrMatcher trimmer) {
+ if (trimmer != null) {
+ this.trimmerMatcher = trimmer;
+ }
+ return this;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets whether the tokenizer should return empty tokens as null.
+ * The default for this property is false.
+ *
+ * @param emptyAsNull whether empty tokens are returned as null
+ * @return this, to enable chaining
+ */
+ 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;
+ }
+ return new String(chars);
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public String toString() {
+ if (tokens == null) {
+ return "StrTokenizer[not tokenized yet]";
+ }
+ return "StrTokenizer" + getTokenList();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/WordUtils.java b/src/main/java/org/apache/commons/lang3/text/WordUtils.java
new file mode 100644
index 000000000..d5ca6eaa9
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/WordUtils.java
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Operations on Strings that contain words.
+ *
+ * <p>This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail.</p>
+ *
+ * @since 2.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/WordUtils.html">
+ * WordUtils</a> instead
+ */
+@Deprecated
+public class WordUtils {
+
+ /**
+ * {@link WordUtils} instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * {@code WordUtils.wrap("foo bar", 20);}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public WordUtils() {
+ }
+
+ /**
+ * Wraps a single line of text, identifying words by {@code ' '}.
+ *
+ * <p>New lines will be separated by the system property line separator.
+ * Very long words, such as URLs will <i>not</i> be wrapped.</p>
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1">
+ * <caption>Examples</caption>
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLength</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here, https://commons.apache.org, to jump to the commons website"</td>
+ * <td>20</td>
+ * <td>"Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"</td>
+ * </tr>
+ * </table>
+ *
+ * (assuming that '\n' is the systems line separator)
+ *
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @return a line with newlines inserted, {@code null} if null input
+ */
+ public static String wrap(final String str, final int wrapLength) {
+ return wrap(str, wrapLength, null, false);
+ }
+
+ /**
+ * Wraps a single line of text, identifying words by {@code ' '}.
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1">
+ * <caption>Examples</caption>
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLength</th>
+ * <th>newLineString</th>
+ * <th>wrapLongWords</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"&lt;br /&gt;"</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of&lt;br /&gt;text that is going&lt;br /&gt;to be wrapped after&lt;br /&gt;20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>null</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>false</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
+ * </tr>
+ * </table>
+ *
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @param newLineStr the string to insert for a new line,
+ * {@code null} uses the system property line separator
+ * @param wrapLongWords true if long words (such as URLs) should be wrapped
+ * @return a line with newlines inserted, {@code null} if null input
+ */
+ public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
+ return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
+ }
+
+ /**
+ * Wraps a single line of text, identifying words by {@code wrapOn}.
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1">
+ * <caption>Examples</caption>
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLength</th>
+ * <th>newLineString</th>
+ * <th>wrapLongWords</th>
+ * <th>wrapOn</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>*</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>*</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"&lt;br /&gt;"</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of&lt;br /&gt;text that is going&lt;br /&gt;to be wrapped after&lt;br /&gt;20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>null</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>false</td>
+ * <td>" "</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>" "</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"flammable/inflammable"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>"/"</td>
+ * <td>"flammable\ninflammable"</td>
+ * </tr>
+ * </table>
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @param newLineStr the string to insert for a new line,
+ * {@code null} uses the system property line separator
+ * @param wrapLongWords true if long words (such as URLs) should be wrapped
+ * @param wrapOn regex expression to be used as a breakable characters,
+ * if blank string is provided a space character will be used
+ * @return a line with newlines inserted, {@code null} if null input
+ */
+ public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
+ if (str == null) {
+ return null;
+ }
+ if (newLineStr == null) {
+ newLineStr = System.lineSeparator();
+ }
+ if (wrapLength < 1) {
+ wrapLength = 1;
+ }
+ if (StringUtils.isBlank(wrapOn)) {
+ wrapOn = " ";
+ }
+ final Pattern patternToWrapOn = Pattern.compile(wrapOn);
+ final int inputLineLength = str.length();
+ int offset = 0;
+ final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
+
+ while (offset < inputLineLength) {
+ int spaceToWrapAt = -1;
+ Matcher matcher = patternToWrapOn.matcher(
+ str.substring(offset, Math.min((int) Math.min(Integer.MAX_VALUE, offset + wrapLength + 1L), inputLineLength)));
+ if (matcher.find()) {
+ if (matcher.start() == 0) {
+ offset += matcher.end();
+ continue;
+ }
+ spaceToWrapAt = matcher.start() + offset;
+ }
+
+ // only last line without leading spaces is left
+ if (inputLineLength - offset <= wrapLength) {
+ break;
+ }
+
+ while (matcher.find()) {
+ spaceToWrapAt = matcher.start() + offset;
+ }
+
+ if (spaceToWrapAt >= offset) {
+ // normal case
+ wrappedLine.append(str, offset, spaceToWrapAt);
+ wrappedLine.append(newLineStr);
+ offset = spaceToWrapAt + 1;
+
+ } else // really long word or URL
+ if (wrapLongWords) {
+ // wrap really long word one line at a time
+ wrappedLine.append(str, offset, wrapLength + offset);
+ wrappedLine.append(newLineStr);
+ offset += wrapLength;
+ } else {
+ // do not wrap really long word, just extend beyond limit
+ matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
+ if (matcher.find()) {
+ spaceToWrapAt = matcher.start() + offset + wrapLength;
+ }
+
+ if (spaceToWrapAt >= 0) {
+ wrappedLine.append(str, offset, spaceToWrapAt);
+ wrappedLine.append(newLineStr);
+ offset = spaceToWrapAt + 1;
+ } else {
+ wrappedLine.append(str, offset, str.length());
+ offset = inputLineLength;
+ }
+ }
+ }
+
+ // Whatever is left in line is short enough to just pass through
+ wrappedLine.append(str, offset, str.length());
+
+ 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)}.
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * WordUtils.capitalize(null) = null
+ * WordUtils.capitalize("") = ""
+ * WordUtils.capitalize("i am FINE") = "I Am FINE"
+ * </pre>
+ *
+ * @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 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[])}.
+ *
+ * <p>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.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * 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"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * WordUtils.capitalizeFully(null) = null
+ * WordUtils.capitalizeFully("") = ""
+ * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>A {@code null} input String returns {@code null}.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * WordUtils.capitalizeFully(null, *) = null
+ * WordUtils.capitalizeFully("", *) = ""
+ * WordUtils.capitalizeFully(*, null) = *
+ * WordUtils.capitalizeFully(*, new char[0]) = *
+ * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * WordUtils.uncapitalize(null) = null
+ * WordUtils.uncapitalize("") = ""
+ * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * WordUtils.uncapitalize(null, *) = null
+ * WordUtils.uncapitalize("", *) = ""
+ * WordUtils.uncapitalize(*, null) = *
+ * WordUtils.uncapitalize(*, new char[0]) = *
+ * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
+ * </pre>
+ *
+ * @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.
+ *
+ * <ul>
+ * <li>Upper case character converts to Lower case</li>
+ * <li>Title case character converts to Lower case</li>
+ * <li>Lower case character after Whitespace or at start converts to Title case</li>
+ * <li>Other Lower case character converts to Upper case</li>
+ * </ul>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>All first characters after whitespace are returned as a new string.
+ * Their case is not changed.</p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A {@code null} input String returns {@code null}.</p>
+ *
+ * <pre>
+ * WordUtils.initials(null) = null
+ * WordUtils.initials("") = ""
+ * WordUtils.initials("Ben John Lee") = "BJL"
+ * WordUtils.initials("Ben J.Lee") = "BJ"
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>All first characters after the defined delimiters are returned as a new string.
+ * Their case is not changed.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * 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]) = ""
+ * </pre>
+ *
+ * @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.
+ *
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/package-info.java b/src/main/java/org/apache/commons/lang3/text/package-info.java
new file mode 100644
index 000000000..29dc886fa
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides classes for handling and manipulating text, partly as an extension to {@link java.text}.
+ * The classes in this package are, for the most part, intended to be instantiated (i.e. they are not utility classes
+ * with lots of static methods).
+ *
+ * <p>Amongst other classes, the text package provides a replacement for {@link java.lang.StringBuffer} named {@link org.apache.commons.lang3.text.StrBuilder}, a class for substituting variables within a String named {@link org.apache.commons.lang3.text.StrSubstitutor} and a replacement for {@link java.util.StringTokenizer} named {@link org.apache.commons.lang3.text.StrTokenizer}.
+ * While somewhat ungainly, the {@code Str} prefix has been used to ensure we don't clash with any current or future standard Java classes.</p>
+ *
+ * @since 2.1
+ * @deprecated As of 3.6, use the Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/package-summary.html">
+ * text package</a>.
+ */
+package org.apache.commons.lang3.text;
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java b/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java
new file mode 100644
index 000000000..1450b2e3b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Executes a sequence of translators one after the other. Execution ends whenever
+ * the first translator consumes code points from the input.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/AggregateTranslator.html">
+ * AggregateTranslator</a> instead
+ */
+@Deprecated
+public class AggregateTranslator extends CharSequenceTranslator {
+
+ private final CharSequenceTranslator[] translators;
+
+ /**
+ * Specify the translators to be used at creation time.
+ *
+ * @param translators CharSequenceTranslator array to aggregate
+ */
+ public AggregateTranslator(final CharSequenceTranslator... translators) {
+ this.translators = ArrayUtils.clone(translators);
+ }
+
+ /**
+ * The first translator to consume code points from the input is the 'winner'.
+ * Execution stops with the number of consumed code points being returned.
+ * {@inheritDoc}
+ */
+ @Override
+ public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ for (final CharSequenceTranslator translator : translators) {
+ final int consumed = translator.translate(input, index, out);
+ if (consumed != 0) {
+ return consumed;
+ }
+ }
+ return 0;
+ }
+
+}
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
new file mode 100644
index 000000000..afbb91960
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * An API for translating text.
+ * Its core use is to escape and unescape text. Because escaping and unescaping
+ * is completely contextual, the API does not present two separate signatures.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/CharSequenceTranslator.html">
+ * CharSequenceTranslator</a> instead
+ */
+@Deprecated
+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.
+ *
+ * @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;
+
+ /**
+ * Helper for non-Writer usage.
+ * @param input CharSequence to be translated
+ * @return String output of translation
+ */
+ public final String translate(final CharSequence input) {
+ if (input == null) {
+ return null;
+ }
+ try {
+ final StringWriter writer = new StringWriter(input.length() * 2);
+ translate(input, writer);
+ return writer.toString();
+ } catch (final IOException ioe) {
+ // this should never ever happen while writing to a StringWriter
+ throw new UncheckedIOException(ioe);
+ }
+ }
+
+ /**
+ * Translate an input onto a Writer. This is intentionally final as its algorithm is
+ * tightly coupled with the abstract method of this class.
+ *
+ * @param input CharSequence that is being translated
+ * @param writer Writer to translate the text to
+ * @throws IOException if and only if the Writer produces an IOException
+ */
+ public final void translate(final CharSequence input, final Writer writer) throws IOException {
+ Objects.requireNonNull(writer, "writer");
+ if (input == null) {
+ return;
+ }
+ int pos = 0;
+ final int len = input.length();
+ while (pos < len) {
+ final int consumed = translate(input, pos, writer);
+ if (consumed == 0) {
+ // inlined implementation of Character.toChars(Character.codePointAt(input, pos))
+ // avoids allocating temp char arrays and duplicate checks
+ final char c1 = input.charAt(pos);
+ writer.write(c1);
+ pos++;
+ if (Character.isHighSurrogate(c1) && pos < len) {
+ final char c2 = input.charAt(pos);
+ if (Character.isLowSurrogate(c2)) {
+ writer.write(c2);
+ pos++;
+ }
+ }
+ continue;
+ }
+ // contract with translators is that they have to understand code points
+ // and they just took care of a surrogate pair
+ for (int pt = 0; pt < consumed; pt++) {
+ pos += Character.charCount(Character.codePointAt(input, pos));
+ }
+ }
+ }
+
+ /**
+ * Helper method to create a merger of this translator with another set of
+ * translators. Useful in customizing the standard functionality.
+ *
+ * @param translators CharSequenceTranslator array of translators to merge with this one
+ * @return CharSequenceTranslator merging this translator with the others
+ */
+ public final CharSequenceTranslator with(final CharSequenceTranslator... translators) {
+ final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1];
+ newArray[0] = this;
+ System.arraycopy(translators, 0, newArray, 1, translators.length);
+ 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/CodePointTranslator.java b/src/main/java/org/apache/commons/lang3/text/translate/CodePointTranslator.java
new file mode 100644
index 000000000..e315fad5b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/CodePointTranslator.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Helper subclass to CharSequenceTranslator to allow for translations that
+ * will replace up to one character at a time.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/CodePointTranslator.html">
+ * CharSequenceTranslator</a> instead
+ */
+@Deprecated
+public abstract class CodePointTranslator extends CharSequenceTranslator {
+
+ /**
+ * Implementation of translate that maps onto the abstract translate(int, Writer) method.
+ * {@inheritDoc}
+ */
+ @Override
+ public final int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ final int codePoint = Character.codePointAt(input, index);
+ final boolean consumed = translate(codePoint, out);
+ return consumed ? 1 : 0;
+ }
+
+ /**
+ * Translate the specified code point into another.
+ *
+ * @param codePoint int character input to translate
+ * @param out Writer to optionally push the translated output to
+ * @return boolean as to whether translation occurred or not
+ * @throws IOException if and only if the Writer produces an IOException
+ */
+ public abstract boolean translate(int codePoint, Writer out) throws IOException;
+
+}
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
new file mode 100644
index 000000000..520455418
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+/**
+ * Class holding various entity data for HTML and XML - generally for use with
+ * the LookupTranslator.
+ * All arrays are of length [*][2].
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/CodePointTranslator.html">
+ * EntityArrays</a> instead
+ */
+@Deprecated
+public class EntityArrays {
+
+ /**
+ * Mapping to escape <a href="https://secure.wikimedia.org/wikipedia/en/wiki/ISO/IEC_8859-1">ISO-8859-1</a>
+ * 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", "&nbsp;"}, // non-breaking space
+ {"\u00A1", "&iexcl;"}, // inverted exclamation mark
+ {"\u00A2", "&cent;"}, // cent sign
+ {"\u00A3", "&pound;"}, // pound sign
+ {"\u00A4", "&curren;"}, // currency sign
+ {"\u00A5", "&yen;"}, // yen sign = yuan sign
+ {"\u00A6", "&brvbar;"}, // broken bar = broken vertical bar
+ {"\u00A7", "&sect;"}, // section sign
+ {"\u00A8", "&uml;"}, // diaeresis = spacing diaeresis
+ {"\u00A9", "&copy;"}, // © - copyright sign
+ {"\u00AA", "&ordf;"}, // feminine ordinal indicator
+ {"\u00AB", "&laquo;"}, // left-pointing double angle quotation mark = left pointing guillemet
+ {"\u00AC", "&not;"}, // not sign
+ {"\u00AD", "&shy;"}, // soft hyphen = discretionary hyphen
+ {"\u00AE", "&reg;"}, // ® - registered trademark sign
+ {"\u00AF", "&macr;"}, // macron = spacing macron = overline = APL overbar
+ {"\u00B0", "&deg;"}, // degree sign
+ {"\u00B1", "&plusmn;"}, // plus-minus sign = plus-or-minus sign
+ {"\u00B2", "&sup2;"}, // superscript two = superscript digit two = squared
+ {"\u00B3", "&sup3;"}, // superscript three = superscript digit three = cubed
+ {"\u00B4", "&acute;"}, // acute accent = spacing acute
+ {"\u00B5", "&micro;"}, // micro sign
+ {"\u00B6", "&para;"}, // pilcrow sign = paragraph sign
+ {"\u00B7", "&middot;"}, // middle dot = Georgian comma = Greek middle dot
+ {"\u00B8", "&cedil;"}, // cedilla = spacing cedilla
+ {"\u00B9", "&sup1;"}, // superscript one = superscript digit one
+ {"\u00BA", "&ordm;"}, // masculine ordinal indicator
+ {"\u00BB", "&raquo;"}, // right-pointing double angle quotation mark = right pointing guillemet
+ {"\u00BC", "&frac14;"}, // vulgar fraction one quarter = fraction one quarter
+ {"\u00BD", "&frac12;"}, // vulgar fraction one half = fraction one half
+ {"\u00BE", "&frac34;"}, // vulgar fraction three quarters = fraction three quarters
+ {"\u00BF", "&iquest;"}, // inverted question mark = turned question mark
+ {"\u00C0", "&Agrave;"}, // À - uppercase A, grave accent
+ {"\u00C1", "&Aacute;"}, // Á - uppercase A, acute accent
+ {"\u00C2", "&Acirc;"}, // Â - uppercase A, circumflex accent
+ {"\u00C3", "&Atilde;"}, // Ã - uppercase A, tilde
+ {"\u00C4", "&Auml;"}, // Ä - uppercase A, umlaut
+ {"\u00C5", "&Aring;"}, // Å - uppercase A, ring
+ {"\u00C6", "&AElig;"}, // Æ - uppercase AE
+ {"\u00C7", "&Ccedil;"}, // Ç - uppercase C, cedilla
+ {"\u00C8", "&Egrave;"}, // È - uppercase E, grave accent
+ {"\u00C9", "&Eacute;"}, // É - uppercase E, acute accent
+ {"\u00CA", "&Ecirc;"}, // Ê - uppercase E, circumflex accent
+ {"\u00CB", "&Euml;"}, // Ë - uppercase E, umlaut
+ {"\u00CC", "&Igrave;"}, // Ì - uppercase I, grave accent
+ {"\u00CD", "&Iacute;"}, // Í - uppercase I, acute accent
+ {"\u00CE", "&Icirc;"}, // Î - uppercase I, circumflex accent
+ {"\u00CF", "&Iuml;"}, // Ï - uppercase I, umlaut
+ {"\u00D0", "&ETH;"}, // Ð - uppercase Eth, Icelandic
+ {"\u00D1", "&Ntilde;"}, // Ñ - uppercase N, tilde
+ {"\u00D2", "&Ograve;"}, // Ò - uppercase O, grave accent
+ {"\u00D3", "&Oacute;"}, // Ó - uppercase O, acute accent
+ {"\u00D4", "&Ocirc;"}, // Ô - uppercase O, circumflex accent
+ {"\u00D5", "&Otilde;"}, // Õ - uppercase O, tilde
+ {"\u00D6", "&Ouml;"}, // Ö - uppercase O, umlaut
+ {"\u00D7", "&times;"}, // multiplication sign
+ {"\u00D8", "&Oslash;"}, // Ø - uppercase O, slash
+ {"\u00D9", "&Ugrave;"}, // Ù - uppercase U, grave accent
+ {"\u00DA", "&Uacute;"}, // Ú - uppercase U, acute accent
+ {"\u00DB", "&Ucirc;"}, // Û - uppercase U, circumflex accent
+ {"\u00DC", "&Uuml;"}, // Ü - uppercase U, umlaut
+ {"\u00DD", "&Yacute;"}, // Ý - uppercase Y, acute accent
+ {"\u00DE", "&THORN;"}, // Þ - uppercase THORN, Icelandic
+ {"\u00DF", "&szlig;"}, // ß - lowercase sharps, German
+ {"\u00E0", "&agrave;"}, // à - lowercase a, grave accent
+ {"\u00E1", "&aacute;"}, // á - lowercase a, acute accent
+ {"\u00E2", "&acirc;"}, // â - lowercase a, circumflex accent
+ {"\u00E3", "&atilde;"}, // ã - lowercase a, tilde
+ {"\u00E4", "&auml;"}, // ä - lowercase a, umlaut
+ {"\u00E5", "&aring;"}, // å - lowercase a, ring
+ {"\u00E6", "&aelig;"}, // æ - lowercase ae
+ {"\u00E7", "&ccedil;"}, // ç - lowercase c, cedilla
+ {"\u00E8", "&egrave;"}, // è - lowercase e, grave accent
+ {"\u00E9", "&eacute;"}, // é - lowercase e, acute accent
+ {"\u00EA", "&ecirc;"}, // ê - lowercase e, circumflex accent
+ {"\u00EB", "&euml;"}, // ë - lowercase e, umlaut
+ {"\u00EC", "&igrave;"}, // ì - lowercase i, grave accent
+ {"\u00ED", "&iacute;"}, // í - lowercase i, acute accent
+ {"\u00EE", "&icirc;"}, // î - lowercase i, circumflex accent
+ {"\u00EF", "&iuml;"}, // ï - lowercase i, umlaut
+ {"\u00F0", "&eth;"}, // ð - lowercase eth, Icelandic
+ {"\u00F1", "&ntilde;"}, // ñ - lowercase n, tilde
+ {"\u00F2", "&ograve;"}, // ò - lowercase o, grave accent
+ {"\u00F3", "&oacute;"}, // ó - lowercase o, acute accent
+ {"\u00F4", "&ocirc;"}, // ô - lowercase o, circumflex accent
+ {"\u00F5", "&otilde;"}, // õ - lowercase o, tilde
+ {"\u00F6", "&ouml;"}, // ö - lowercase o, umlaut
+ {"\u00F7", "&divide;"}, // division sign
+ {"\u00F8", "&oslash;"}, // ø - lowercase o, slash
+ {"\u00F9", "&ugrave;"}, // ù - lowercase u, grave accent
+ {"\u00FA", "&uacute;"}, // ú - lowercase u, acute accent
+ {"\u00FB", "&ucirc;"}, // û - lowercase u, circumflex accent
+ {"\u00FC", "&uuml;"}, // ü - lowercase u, umlaut
+ {"\u00FD", "&yacute;"}, // ý - lowercase y, acute accent
+ {"\u00FE", "&thorn;"}, // þ - lowercase thorn, Icelandic
+ {"\u00FF", "&yuml;"}, // ÿ - 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 <a href="https://www.w3.org/TR/REC-html40/sgml/entities.html">character entity
+ * references</a>. 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 = {
+ // <!-- Latin Extended-B -->
+ {"\u0192", "&fnof;"}, // latin small f with hook = function= florin, U+0192 ISOtech -->
+ // <!-- Greek -->
+ {"\u0391", "&Alpha;"}, // greek capital letter alpha, U+0391 -->
+ {"\u0392", "&Beta;"}, // greek capital letter beta, U+0392 -->
+ {"\u0393", "&Gamma;"}, // greek capital letter gamma, U+0393 ISOgrk3 -->
+ {"\u0394", "&Delta;"}, // greek capital letter delta, U+0394 ISOgrk3 -->
+ {"\u0395", "&Epsilon;"}, // greek capital letter epsilon, U+0395 -->
+ {"\u0396", "&Zeta;"}, // greek capital letter zeta, U+0396 -->
+ {"\u0397", "&Eta;"}, // greek capital letter eta, U+0397 -->
+ {"\u0398", "&Theta;"}, // greek capital letter theta, U+0398 ISOgrk3 -->
+ {"\u0399", "&Iota;"}, // greek capital letter iota, U+0399 -->
+ {"\u039A", "&Kappa;"}, // greek capital letter kappa, U+039A -->
+ {"\u039B", "&Lambda;"}, // greek capital letter lambda, U+039B ISOgrk3 -->
+ {"\u039C", "&Mu;"}, // greek capital letter mu, U+039C -->
+ {"\u039D", "&Nu;"}, // greek capital letter nu, U+039D -->
+ {"\u039E", "&Xi;"}, // greek capital letter xi, U+039E ISOgrk3 -->
+ {"\u039F", "&Omicron;"}, // greek capital letter omicron, U+039F -->
+ {"\u03A0", "&Pi;"}, // greek capital letter pi, U+03A0 ISOgrk3 -->
+ {"\u03A1", "&Rho;"}, // greek capital letter rho, U+03A1 -->
+ // <!-- there is no Sigmaf, and no U+03A2 character either -->
+ {"\u03A3", "&Sigma;"}, // greek capital letter sigma, U+03A3 ISOgrk3 -->
+ {"\u03A4", "&Tau;"}, // greek capital letter tau, U+03A4 -->
+ {"\u03A5", "&Upsilon;"}, // greek capital letter upsilon, U+03A5 ISOgrk3 -->
+ {"\u03A6", "&Phi;"}, // greek capital letter phi, U+03A6 ISOgrk3 -->
+ {"\u03A7", "&Chi;"}, // greek capital letter chi, U+03A7 -->
+ {"\u03A8", "&Psi;"}, // greek capital letter psi, U+03A8 ISOgrk3 -->
+ {"\u03A9", "&Omega;"}, // greek capital letter omega, U+03A9 ISOgrk3 -->
+ {"\u03B1", "&alpha;"}, // greek small letter alpha, U+03B1 ISOgrk3 -->
+ {"\u03B2", "&beta;"}, // greek small letter beta, U+03B2 ISOgrk3 -->
+ {"\u03B3", "&gamma;"}, // greek small letter gamma, U+03B3 ISOgrk3 -->
+ {"\u03B4", "&delta;"}, // greek small letter delta, U+03B4 ISOgrk3 -->
+ {"\u03B5", "&epsilon;"}, // greek small letter epsilon, U+03B5 ISOgrk3 -->
+ {"\u03B6", "&zeta;"}, // greek small letter zeta, U+03B6 ISOgrk3 -->
+ {"\u03B7", "&eta;"}, // greek small letter eta, U+03B7 ISOgrk3 -->
+ {"\u03B8", "&theta;"}, // greek small letter theta, U+03B8 ISOgrk3 -->
+ {"\u03B9", "&iota;"}, // greek small letter iota, U+03B9 ISOgrk3 -->
+ {"\u03BA", "&kappa;"}, // greek small letter kappa, U+03BA ISOgrk3 -->
+ {"\u03BB", "&lambda;"}, // greek small letter lambda, U+03BB ISOgrk3 -->
+ {"\u03BC", "&mu;"}, // greek small letter mu, U+03BC ISOgrk3 -->
+ {"\u03BD", "&nu;"}, // greek small letter nu, U+03BD ISOgrk3 -->
+ {"\u03BE", "&xi;"}, // greek small letter xi, U+03BE ISOgrk3 -->
+ {"\u03BF", "&omicron;"}, // greek small letter omicron, U+03BF NEW -->
+ {"\u03C0", "&pi;"}, // greek small letter pi, U+03C0 ISOgrk3 -->
+ {"\u03C1", "&rho;"}, // greek small letter rho, U+03C1 ISOgrk3 -->
+ {"\u03C2", "&sigmaf;"}, // greek small letter final sigma, U+03C2 ISOgrk3 -->
+ {"\u03C3", "&sigma;"}, // greek small letter sigma, U+03C3 ISOgrk3 -->
+ {"\u03C4", "&tau;"}, // greek small letter tau, U+03C4 ISOgrk3 -->
+ {"\u03C5", "&upsilon;"}, // greek small letter upsilon, U+03C5 ISOgrk3 -->
+ {"\u03C6", "&phi;"}, // greek small letter phi, U+03C6 ISOgrk3 -->
+ {"\u03C7", "&chi;"}, // greek small letter chi, U+03C7 ISOgrk3 -->
+ {"\u03C8", "&psi;"}, // greek small letter psi, U+03C8 ISOgrk3 -->
+ {"\u03C9", "&omega;"}, // greek small letter omega, U+03C9 ISOgrk3 -->
+ {"\u03D1", "&thetasym;"}, // greek small letter theta symbol, U+03D1 NEW -->
+ {"\u03D2", "&upsih;"}, // greek upsilon with hook symbol, U+03D2 NEW -->
+ {"\u03D6", "&piv;"}, // greek pi symbol, U+03D6 ISOgrk3 -->
+ // <!-- General Punctuation -->
+ {"\u2022", "&bull;"}, // bullet = black small circle, U+2022 ISOpub -->
+ // <!-- bullet is NOT the same as bullet operator, U+2219 -->
+ {"\u2026", "&hellip;"}, // horizontal ellipsis = three dot leader, U+2026 ISOpub -->
+ {"\u2032", "&prime;"}, // prime = minutes = feet, U+2032 ISOtech -->
+ {"\u2033", "&Prime;"}, // double prime = seconds = inches, U+2033 ISOtech -->
+ {"\u203E", "&oline;"}, // overline = spacing overscore, U+203E NEW -->
+ {"\u2044", "&frasl;"}, // fraction slash, U+2044 NEW -->
+ // <!-- Letterlike Symbols -->
+ {"\u2118", "&weierp;"}, // script capital P = power set= Weierstrass p, U+2118 ISOamso -->
+ {"\u2111", "&image;"}, // blackletter capital I = imaginary part, U+2111 ISOamso -->
+ {"\u211C", "&real;"}, // blackletter capital R = real part symbol, U+211C ISOamso -->
+ {"\u2122", "&trade;"}, // trade mark sign, U+2122 ISOnum -->
+ {"\u2135", "&alefsym;"}, // alef symbol = first transfinite cardinal, U+2135 NEW -->
+ // <!-- alef symbol is NOT the same as hebrew letter alef, U+05D0 although the
+ // same glyph could be used to depict both characters -->
+ // <!-- Arrows -->
+ {"\u2190", "&larr;"}, // leftwards arrow, U+2190 ISOnum -->
+ {"\u2191", "&uarr;"}, // upwards arrow, U+2191 ISOnum-->
+ {"\u2192", "&rarr;"}, // rightwards arrow, U+2192 ISOnum -->
+ {"\u2193", "&darr;"}, // downwards arrow, U+2193 ISOnum -->
+ {"\u2194", "&harr;"}, // left right arrow, U+2194 ISOamsa -->
+ {"\u21B5", "&crarr;"}, // downwards arrow with corner leftwards= carriage return, U+21B5 NEW -->
+ {"\u21D0", "&lArr;"}, // leftwards double arrow, U+21D0 ISOtech -->
+ // <!-- ISO 10646 does not say that lArr is the same as the 'is implied by'
+ // arrow but also does not have any other character for that function.
+ // So ? lArr canbe used for 'is implied by' as ISOtech suggests -->
+ {"\u21D1", "&uArr;"}, // upwards double arrow, U+21D1 ISOamsa -->
+ {"\u21D2", "&rArr;"}, // rightwards double arrow, U+21D2 ISOtech -->
+ // <!-- ISO 10646 does not say this is the 'implies' character but does not
+ // have another character with this function so ?rArr can be used for
+ // 'implies' as ISOtech suggests -->
+ {"\u21D3", "&dArr;"}, // downwards double arrow, U+21D3 ISOamsa -->
+ {"\u21D4", "&hArr;"}, // left right double arrow, U+21D4 ISOamsa -->
+ // <!-- Mathematical Operators -->
+ {"\u2200", "&forall;"}, // for all, U+2200 ISOtech -->
+ {"\u2202", "&part;"}, // partial differential, U+2202 ISOtech -->
+ {"\u2203", "&exist;"}, // there exists, U+2203 ISOtech -->
+ {"\u2205", "&empty;"}, // empty set = null set = diameter, U+2205 ISOamso -->
+ {"\u2207", "&nabla;"}, // nabla = backward difference, U+2207 ISOtech -->
+ {"\u2208", "&isin;"}, // element of, U+2208 ISOtech -->
+ {"\u2209", "&notin;"}, // not an element of, U+2209 ISOtech -->
+ {"\u220B", "&ni;"}, // contains as member, U+220B ISOtech -->
+ // <!-- should there be a more memorable name than 'ni'? -->
+ {"\u220F", "&prod;"}, // n-ary product = product sign, U+220F ISOamsb -->
+ // <!-- prod is NOT the same character as U+03A0 'greek capital letter pi'
+ // though the same glyph might be used for both -->
+ {"\u2211", "&sum;"}, // n-ary summation, U+2211 ISOamsb -->
+ // <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+ // though the same glyph might be used for both -->
+ {"\u2212", "&minus;"}, // minus sign, U+2212 ISOtech -->
+ {"\u2217", "&lowast;"}, // asterisk operator, U+2217 ISOtech -->
+ {"\u221A", "&radic;"}, // square root = radical sign, U+221A ISOtech -->
+ {"\u221D", "&prop;"}, // proportional to, U+221D ISOtech -->
+ {"\u221E", "&infin;"}, // infinity, U+221E ISOtech -->
+ {"\u2220", "&ang;"}, // angle, U+2220 ISOamso -->
+ {"\u2227", "&and;"}, // logical and = wedge, U+2227 ISOtech -->
+ {"\u2228", "&or;"}, // logical or = vee, U+2228 ISOtech -->
+ {"\u2229", "&cap;"}, // intersection = cap, U+2229 ISOtech -->
+ {"\u222A", "&cup;"}, // union = cup, U+222A ISOtech -->
+ {"\u222B", "&int;"}, // integral, U+222B ISOtech -->
+ {"\u2234", "&there4;"}, // therefore, U+2234 ISOtech -->
+ {"\u223C", "&sim;"}, // tilde operator = varies with = similar to, U+223C ISOtech -->
+ // <!-- tilde operator is NOT the same character as the tilde, U+007E, although
+ // the same glyph might be used to represent both -->
+ {"\u2245", "&cong;"}, // approximately equal to, U+2245 ISOtech -->
+ {"\u2248", "&asymp;"}, // almost equal to = asymptotic to, U+2248 ISOamsr -->
+ {"\u2260", "&ne;"}, // not equal to, U+2260 ISOtech -->
+ {"\u2261", "&equiv;"}, // identical to, U+2261 ISOtech -->
+ {"\u2264", "&le;"}, // less-than or equal to, U+2264 ISOtech -->
+ {"\u2265", "&ge;"}, // greater-than or equal to, U+2265 ISOtech -->
+ {"\u2282", "&sub;"}, // subset of, U+2282 ISOtech -->
+ {"\u2283", "&sup;"}, // superset of, U+2283 ISOtech -->
+ // <!-- note that nsup, 'not a superset of, U+2283' is not covered by the
+ // Symbol font encoding and is not included. Should it be, for symmetry?
+ // It is in ISOamsn -->,
+ {"\u2284", "&nsub;"}, // not a subset of, U+2284 ISOamsn -->
+ {"\u2286", "&sube;"}, // subset of or equal to, U+2286 ISOtech -->
+ {"\u2287", "&supe;"}, // superset of or equal to, U+2287 ISOtech -->
+ {"\u2295", "&oplus;"}, // circled plus = direct sum, U+2295 ISOamsb -->
+ {"\u2297", "&otimes;"}, // circled times = vector product, U+2297 ISOamsb -->
+ {"\u22A5", "&perp;"}, // up tack = orthogonal to = perpendicular, U+22A5 ISOtech -->
+ {"\u22C5", "&sdot;"}, // dot operator, U+22C5 ISOamsb -->
+ // <!-- dot operator is NOT the same character as U+00B7 middle dot -->
+ // <!-- Miscellaneous Technical -->
+ {"\u2308", "&lceil;"}, // left ceiling = apl upstile, U+2308 ISOamsc -->
+ {"\u2309", "&rceil;"}, // right ceiling, U+2309 ISOamsc -->
+ {"\u230A", "&lfloor;"}, // left floor = apl downstile, U+230A ISOamsc -->
+ {"\u230B", "&rfloor;"}, // right floor, U+230B ISOamsc -->
+ {"\u2329", "&lang;"}, // left-pointing angle bracket = bra, U+2329 ISOtech -->
+ // <!-- lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation
+ // mark' -->
+ {"\u232A", "&rang;"}, // right-pointing angle bracket = ket, U+232A ISOtech -->
+ // <!-- rang is NOT the same character as U+003E 'greater than' or U+203A
+ // 'single right-pointing angle quotation mark' -->
+ // <!-- Geometric Shapes -->
+ {"\u25CA", "&loz;"}, // lozenge, U+25CA ISOpub -->
+ // <!-- Miscellaneous Symbols -->
+ {"\u2660", "&spades;"}, // black spade suit, U+2660 ISOpub -->
+ // <!-- black here seems to mean filled as opposed to hollow -->
+ {"\u2663", "&clubs;"}, // black club suit = shamrock, U+2663 ISOpub -->
+ {"\u2665", "&hearts;"}, // black heart suit = valentine, U+2665 ISOpub -->
+ {"\u2666", "&diams;"}, // black diamond suit, U+2666 ISOpub -->
+
+ // <!-- Latin Extended-A -->
+ {"\u0152", "&OElig;"}, // -- latin capital ligature OE, U+0152 ISOlat2 -->
+ {"\u0153", "&oelig;"}, // -- latin small ligature oe, U+0153 ISOlat2 -->
+ // <!-- ligature is a misnomer, this is a separate character in some languages -->
+ {"\u0160", "&Scaron;"}, // -- latin capital letter S with caron, U+0160 ISOlat2 -->
+ {"\u0161", "&scaron;"}, // -- latin small letter s with caron, U+0161 ISOlat2 -->
+ {"\u0178", "&Yuml;"}, // -- latin capital letter Y with diaeresis, U+0178 ISOlat2 -->
+ // <!-- Spacing Modifier Letters -->
+ {"\u02C6", "&circ;"}, // -- modifier letter circumflex accent, U+02C6 ISOpub -->
+ {"\u02DC", "&tilde;"}, // small tilde, U+02DC ISOdia -->
+ // <!-- General Punctuation -->
+ {"\u2002", "&ensp;"}, // en space, U+2002 ISOpub -->
+ {"\u2003", "&emsp;"}, // em space, U+2003 ISOpub -->
+ {"\u2009", "&thinsp;"}, // thin space, U+2009 ISOpub -->
+ {"\u200C", "&zwnj;"}, // zero width non-joiner, U+200C NEW RFC 2070 -->
+ {"\u200D", "&zwj;"}, // zero width joiner, U+200D NEW RFC 2070 -->
+ {"\u200E", "&lrm;"}, // left-to-right mark, U+200E NEW RFC 2070 -->
+ {"\u200F", "&rlm;"}, // right-to-left mark, U+200F NEW RFC 2070 -->
+ {"\u2013", "&ndash;"}, // en dash, U+2013 ISOpub -->
+ {"\u2014", "&mdash;"}, // em dash, U+2014 ISOpub -->
+ {"\u2018", "&lsquo;"}, // left single quotation mark, U+2018 ISOnum -->
+ {"\u2019", "&rsquo;"}, // right single quotation mark, U+2019 ISOnum -->
+ {"\u201A", "&sbquo;"}, // single low-9 quotation mark, U+201A NEW -->
+ {"\u201C", "&ldquo;"}, // left double quotation mark, U+201C ISOnum -->
+ {"\u201D", "&rdquo;"}, // right double quotation mark, U+201D ISOnum -->
+ {"\u201E", "&bdquo;"}, // double low-9 quotation mark, U+201E NEW -->
+ {"\u2020", "&dagger;"}, // dagger, U+2020 ISOpub -->
+ {"\u2021", "&Dagger;"}, // double dagger, U+2021 ISOpub -->
+ {"\u2030", "&permil;"}, // per mille sign, U+2030 ISOtech -->
+ {"\u2039", "&lsaquo;"}, // single left-pointing angle quotation mark, U+2039 ISO proposed -->
+ // <!-- lsaquo is proposed but not yet ISO standardized -->
+ {"\u203A", "&rsaquo;"}, // single right-pointing angle quotation mark, U+203A ISO proposed -->
+ // <!-- rsaquo is proposed but not yet ISO standardized -->
+ {"\u20AC", "&euro;"}, // -- euro sign, U+20AC NEW -->
+ };
+
+ /**
+ * Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes.
+ * @return the mapping table
+ */
+ public static String[][] HTML40_EXTENDED_UNESCAPE() {
+ return HTML40_EXTENDED_UNESCAPE.clone();
+ }
+
+ private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE);
+
+ /**
+ * Mapping to escape the basic XML and HTML character entities.
+ *
+ * Namely: {@code " & < >}
+ * @return the mapping table
+ */
+ public static String[][] BASIC_ESCAPE() {
+ return BASIC_ESCAPE.clone();
+ }
+
+ private static final String[][] BASIC_ESCAPE = {
+ {"\"", "&quot;"}, // " - double-quote
+ {"&", "&amp;"}, // & - ampersand
+ {"<", "&lt;"}, // < - less-than
+ {">", "&gt;"}, // > - greater-than
+ };
+
+ /**
+ * Reverse of {@link #BASIC_ESCAPE()} for unescaping purposes.
+ * @return the mapping table
+ */
+ public static String[][] BASIC_UNESCAPE() {
+ return BASIC_UNESCAPE.clone();
+ }
+
+ private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE);
+
+ /**
+ * Mapping to escape the apostrophe character to its XML character entity.
+ * @return the mapping table
+ */
+ public static String[][] APOS_ESCAPE() {
+ return APOS_ESCAPE.clone();
+ }
+
+ private static final String[][] APOS_ESCAPE = {
+ {"'", "&apos;"}, // XML apostrophe
+ };
+
+ /**
+ * Reverse of {@link #APOS_ESCAPE()} for unescaping purposes.
+ * @return the mapping table
+ */
+ public static String[][] APOS_UNESCAPE() {
+ return APOS_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
+ * @return String[][] inverted array
+ */
+ public static String[][] invert(final String[][] array) {
+ final String[][] newarray = new String[array.length][2];
+ for (int i = 0; i<array.length; i++) {
+ newarray[i][0] = array[i][1];
+ newarray[i][1] = array[i][0];
+ }
+ return newarray;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/JavaUnicodeEscaper.java b/src/main/java/org/apache/commons/lang3/text/translate/JavaUnicodeEscaper.java
new file mode 100644
index 000000000..64f4c302f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/JavaUnicodeEscaper.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+/**
+ * Translates code points to their Unicode escaped value suitable for Java source.
+ *
+ * @since 3.2
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/UnicodeEscaper.html">
+ * UnicodeEscaper</a> instead
+ */
+@Deprecated
+public class JavaUnicodeEscaper extends UnicodeEscaper {
+
+ /**
+ * Constructs a {@link JavaUnicodeEscaper} above the specified value (exclusive).
+ *
+ * @param codePoint
+ * above which to escape
+ * @return the newly created {@link UnicodeEscaper} instance
+ */
+ public static JavaUnicodeEscaper above(final int codePoint) {
+ return outsideOf(0, codePoint);
+ }
+
+ /**
+ * Constructs a {@link JavaUnicodeEscaper} below the specified value (exclusive).
+ *
+ * @param codePoint
+ * below which to escape
+ * @return the newly created {@link UnicodeEscaper} instance
+ */
+ public static JavaUnicodeEscaper below(final int codePoint) {
+ return outsideOf(codePoint, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Constructs a {@link JavaUnicodeEscaper} 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 JavaUnicodeEscaper between(final int codePointLow, final int codePointHigh) {
+ return new JavaUnicodeEscaper(codePointLow, codePointHigh, true);
+ }
+
+ /**
+ * Constructs a {@link JavaUnicodeEscaper} 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 JavaUnicodeEscaper outsideOf(final int codePointLow, final int codePointHigh) {
+ return new JavaUnicodeEscaper(codePointLow, codePointHigh, false);
+ }
+
+ /**
+ * Constructs a {@link JavaUnicodeEscaper} 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
+ */
+ public JavaUnicodeEscaper(final int below, final int above, final boolean between) {
+ super(below, above, between);
+ }
+
+ /**
+ * Converts the given code point to a hex string of the form {@code "\\uXXXX\\uXXXX"}
+ *
+ * @param codePoint
+ * a Unicode code point
+ * @return the hex string for the given code point
+ */
+ @Override
+ protected String toUtf16Escape(final int codePoint) {
+ final char[] surrogatePair = Character.toChars(codePoint);
+ return "\\u" + hex(surrogatePair[0]) + "\\u" + hex(surrogatePair[1]);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java b/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java
new file mode 100644
index 000000000..dbc9569c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Translates a value using a lookup table.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/LookupTranslator.html">
+ * LookupTranslator</a> instead
+ */
+@Deprecated
+public class LookupTranslator extends CharSequenceTranslator {
+
+ private final HashMap<String, String> lookupMap;
+ private final HashSet<Character> prefixSet;
+ private final int shortest;
+ private final int longest;
+
+ /**
+ * Define the lookup table to be used in translation
+ *
+ * Note that, as of Lang 3.1, the key to the lookup table is converted to a
+ * java.lang.String. This is because we need the key to support hashCode and
+ * equals(Object), allowing it to be the key for a HashMap. See LANG-882.
+ *
+ * @param lookup CharSequence[][] table of size [*][2]
+ */
+ public LookupTranslator(final CharSequence[]... lookup) {
+ lookupMap = new HashMap<>();
+ prefixSet = new HashSet<>();
+ int tmpShortest = Integer.MAX_VALUE;
+ int tmpLongest = 0;
+ if (lookup != null) {
+ for (final CharSequence[] seq : lookup) {
+ this.lookupMap.put(seq[0].toString(), seq[1].toString());
+ this.prefixSet.add(seq[0].charAt(0));
+ final int sz = seq[0].length();
+ if (sz < tmpShortest) {
+ tmpShortest = sz;
+ }
+ if (sz > tmpLongest) {
+ tmpLongest = sz;
+ }
+ }
+ }
+ this.shortest = tmpShortest;
+ this.longest = tmpLongest;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ // check if translation exists for the input at position index
+ if (prefixSet.contains(input.charAt(index))) {
+ int max = longest;
+ if (index + longest > input.length()) {
+ max = input.length() - index;
+ }
+ // implement greedy algorithm by trying maximum match first
+ for (int i = max; i >= shortest; i--) {
+ final CharSequence subSeq = input.subSequence(index, index + i);
+ final String result = lookupMap.get(subSeq.toString());
+
+ if (result != null) {
+ out.write(result);
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+}
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
new file mode 100644
index 000000000..538c7006b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityEscaper.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Translates code points to their XML numeric entity escaped value.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/NumericEntityEscaper.html">
+ * NumericEntityEscaper</a> instead
+ */
+@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).
+ *
+ * @param codePoint above which to escape
+ * @return the newly created {@link NumericEntityEscaper} instance
+ */
+ public static NumericEntityEscaper above(final int codePoint) {
+ return outsideOf(0, codePoint);
+ }
+
+ /**
+ * Constructs a {@link NumericEntityEscaper} between the specified values (inclusive).
+ *
+ * @param codePointLow above which to escape
+ * @param codePointHigh below which to escape
+ * @return the newly created {@link NumericEntityEscaper} instance
+ */
+ public static NumericEntityEscaper between(final int codePointLow, final int codePointHigh) {
+ return new NumericEntityEscaper(codePointLow, codePointHigh, true);
+ }
+
+ /**
+ * Constructs a {@link NumericEntityEscaper} outside of the specified values (exclusive).
+ *
+ * @param codePointLow below which to escape
+ * @param codePointHigh above which to escape
+ * @return the newly created {@link NumericEntityEscaper} instance
+ */
+ public static NumericEntityEscaper outsideOf(final int codePointLow, final int codePointHigh) {
+ return new NumericEntityEscaper(codePointLow, codePointHigh, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean translate(final int codePoint, final Writer out) throws IOException {
+ if (between) {
+ if (codePoint < below || codePoint > above) {
+ return false;
+ }
+ } else if (codePoint >= below && codePoint <= above) {
+ return false;
+ }
+
+ out.write("&#");
+ out.write(Integer.toString(codePoint, 10));
+ out.write(';');
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java b/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java
new file mode 100644
index 000000000..a238c9d43
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+
+/**
+ * Translate XML numeric entities of the form &amp;#[xX]?\d+;? to
+ * the specific code point.
+ *
+ * Note that the semicolon is optional.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/NumericEntityUnescaper.html">
+ * NumericEntityUnescaper</a> instead
+ */
+@Deprecated
+public class NumericEntityUnescaper extends CharSequenceTranslator {
+
+ /** Enumerates NumericEntityUnescaper options for unescaping. */
+ public enum OPTION {
+
+ /**
+ * Require a semicolon.
+ */
+ semiColonRequired,
+
+ /**
+ * Do not require a semicolon.
+ */
+ semiColonOptional,
+
+ /**
+ * Throw an exception if a semicolon is missing.
+ */
+ errorIfNoSemiColon
+ }
+
+ // TODO?: Create an OptionsSet class to hide some of the conditional logic below
+ private final EnumSet<OPTION> options;
+
+ /**
+ * Create a UnicodeUnescaper.
+ *
+ * The constructor takes a list of options, only one type of which is currently
+ * available (whether to allow, error or ignore the semicolon on the end of a
+ * numeric entity to being missing).
+ *
+ * For example, to support numeric entities without a ';':
+ * new NumericEntityUnescaper(NumericEntityUnescaper.OPTION.semiColonOptional)
+ * and to throw an IllegalArgumentException when they're missing:
+ * new NumericEntityUnescaper(NumericEntityUnescaper.OPTION.errorIfNoSemiColon)
+ *
+ * Note that the default behavior is to ignore them.
+ *
+ * @param options to apply to this unescaper
+ */
+ public NumericEntityUnescaper(final OPTION... options) {
+ if (options.length > 0) {
+ this.options = EnumSet.copyOf(Arrays.asList(options));
+ } else {
+ this.options = EnumSet.copyOf(Collections.singletonList(OPTION.semiColonRequired));
+ }
+ }
+
+ /**
+ * Whether the passed in option is currently set.
+ *
+ * @param option to check state of
+ * @return whether the option is set
+ */
+ public boolean isSet(final OPTION option) {
+ return options != null && options.contains(option);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ final int seqEnd = input.length();
+ // Uses -2 to ensure there is something after the &#
+ if (input.charAt(index) == '&' && index < seqEnd - 2 && input.charAt(index + 1) == '#') {
+ int start = index + 2;
+ boolean isHex = false;
+
+ final char firstChar = input.charAt(start);
+ if (firstChar == 'x' || firstChar == 'X') {
+ start++;
+ isHex = true;
+
+ // Check there's more than just an x after the &#
+ if (start == seqEnd) {
+ return 0;
+ }
+ }
+
+ int end = start;
+ // Note that this supports character codes without a ; on the end
+ while (end < seqEnd && ( input.charAt(end) >= '0' && input.charAt(end) <= '9' ||
+ input.charAt(end) >= 'a' && input.charAt(end) <= 'f' ||
+ input.charAt(end) >= 'A' && input.charAt(end) <= 'F' ) ) {
+ end++;
+ }
+
+ final boolean semiNext = end != seqEnd && input.charAt(end) == ';';
+
+ if (!semiNext) {
+ if (isSet(OPTION.semiColonRequired)) {
+ return 0;
+ }
+ if (isSet(OPTION.errorIfNoSemiColon)) {
+ throw new IllegalArgumentException("Semi-colon required at end of numeric entity");
+ }
+ }
+
+ final int entityValue;
+ try {
+ if (isHex) {
+ entityValue = Integer.parseInt(input.subSequence(start, end).toString(), 16);
+ } else {
+ entityValue = Integer.parseInt(input.subSequence(start, end).toString(), 10);
+ }
+ } catch(final NumberFormatException nfe) {
+ return 0;
+ }
+
+ if (entityValue > 0xFFFF) {
+ final char[] chars = Character.toChars(entityValue);
+ out.write(chars[0]);
+ out.write(chars[1]);
+ } else {
+ out.write(entityValue);
+ }
+
+ return 2 + end - start + (isHex ? 1 : 0) + (semiNext ? 1 : 0);
+ }
+ return 0;
+ }
+}
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
new file mode 100644
index 000000000..7f37e449f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/OctalUnescaper.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Translate escaped octal Strings back to their octal values.
+ *
+ * For example, "\45" should go back to being the specific value (a %).
+ *
+ * Note that this currently only supports the viable range of octal for Java; namely
+ * 1 to 377. This is because parsing Java is the main use case.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/OctalUnescaper.html">
+ * OctalUnescaper</a> instead
+ */
+@Deprecated
+public class OctalUnescaper extends CharSequenceTranslator {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ final int remaining = input.length() - index - 1; // how many characters left, ignoring the first \
+ final StringBuilder builder = new StringBuilder();
+ if (input.charAt(index) == '\\' && remaining > 0 && isOctalDigit(input.charAt(index + 1)) ) {
+ final int next = index + 1;
+ final int next2 = index + 2;
+ final int next3 = index + 3;
+
+ // we know this is good as we checked it in the if block above
+ builder.append(input.charAt(next));
+
+ if (remaining > 1 && isOctalDigit(input.charAt(next2))) {
+ builder.append(input.charAt(next2));
+ if (remaining > 2 && isZeroToThree(input.charAt(next)) && isOctalDigit(input.charAt(next3))) {
+ builder.append(input.charAt(next3));
+ }
+ }
+
+ out.write( Integer.parseInt(builder.toString(), 8) );
+ return 1 + builder.length();
+ }
+ 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
new file mode 100644
index 000000000..0f518c0ba
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeEscaper.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Translates code points to their Unicode escaped value.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/UnicodeEscaper.html">
+ * UnicodeEscaper</a> instead
+ */
+@Deprecated
+public class UnicodeEscaper extends CodePointTranslator {
+
+ private final int below;
+ private final int above;
+ private final boolean between;
+
+ /**
+ * Constructs a {@link UnicodeEscaper} for all characters.
+ */
+ public UnicodeEscaper() {
+ this(0, Integer.MAX_VALUE, true);
+ }
+
+ /**
+ * Constructs a {@link UnicodeEscaper} 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
+ */
+ protected UnicodeEscaper(final int below, final int above, final boolean between) {
+ this.below = below;
+ this.above = above;
+ this.between = between;
+ }
+
+ /**
+ * 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} 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} 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);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean translate(final int codePoint, final Writer out) throws IOException {
+ if (between) {
+ if (codePoint < below || codePoint > above) {
+ return false;
+ }
+ } else if (codePoint >= below && codePoint <= above) {
+ return false;
+ }
+
+ // TODO: Handle potential + sign per various Unicode escape implementations
+ if (codePoint > 0xffff) {
+ out.write(toUtf16Escape(codePoint));
+ } else {
+ out.write("\\u");
+ out.write(HEX_DIGITS[(codePoint >> 12) & 15]);
+ out.write(HEX_DIGITS[(codePoint >> 8) & 15]);
+ out.write(HEX_DIGITS[(codePoint >> 4) & 15]);
+ out.write(HEX_DIGITS[(codePoint) & 15]);
+ }
+ return true;
+ }
+
+ /**
+ * Converts the given code point to a hex string of the form {@code "\\uXXXX"}
+ *
+ * @param codePoint
+ * a Unicode code point
+ * @return the hex 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/text/translate/UnicodeUnescaper.java b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnescaper.java
new file mode 100644
index 000000000..cb19617e7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnescaper.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Translates escaped Unicode values of the form \\u+\d\d\d\d back to
+ * Unicode. It supports multiple 'u' characters and will work with or
+ * without the +.
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/UnicodeUnescaper.html">
+ * UnicodeUnescaper</a> instead
+ */
+@Deprecated
+public class UnicodeUnescaper extends CharSequenceTranslator {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ if (input.charAt(index) == '\\' && index + 1 < input.length() && input.charAt(index + 1) == 'u') {
+ // consume optional additional 'u' chars
+ int i = 2;
+ while (index + i < input.length() && input.charAt(index + i) == 'u') {
+ i++;
+ }
+
+ if (index + i < input.length() && input.charAt(index + i) == '+') {
+ i++;
+ }
+
+ if (index + i + 4 <= input.length()) {
+ // Get 4 hex digits
+ final CharSequence unicode = input.subSequence(index + i, index + i + 4);
+
+ try {
+ final int value = Integer.parseInt(unicode.toString(), 16);
+ out.write((char) value);
+ } catch (final NumberFormatException nfe) {
+ throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe);
+ }
+ return i + 4;
+ }
+ throw new IllegalArgumentException("Less than 4 hex digits in unicode value: '" + input.subSequence(index, input.length())
+ + "' due to end of CharSequence");
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemover.java b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemover.java
new file mode 100644
index 000000000..d054742f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/UnicodeUnpairedSurrogateRemover.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.text.translate;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Helper subclass to CharSequenceTranslator to remove unpaired surrogates.
+ *
+ * @deprecated As of 3.6, use Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/UnicodeUnpairedSurrogateRemover.html">
+ * UnicodeUnpairedSurrogateRemover</a> instead
+ */
+@Deprecated
+public class UnicodeUnpairedSurrogateRemover extends CodePointTranslator {
+ /**
+ * Implementation of translate that throws out unpaired surrogates.
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean translate(final int codePoint, final Writer out) throws IOException {
+ // true: It's a surrogate. Write nothing and say we've translated.
+ return codePoint >= Character.MIN_SURROGATE && codePoint <= Character.MAX_SURROGATE;
+ // It's not a surrogate. Don't translate it.
+ }
+}
+
diff --git a/src/main/java/org/apache/commons/lang3/text/translate/package-info.java b/src/main/java/org/apache/commons/lang3/text/translate/package-info.java
new file mode 100644
index 000000000..7c507cac2
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/text/translate/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * An API for creating text translation routines from a set of smaller building blocks. Initially created to make it
+ * possible for the user to customize the rules in the StringEscapeUtils class.
+ * <p>These classes are immutable, and therefore thread-safe.</p>
+ *
+ * @since 3.0
+ * @deprecated As of 3.6, use the Apache Commons Text
+ * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/translate/package-summary.html">
+ * translate package</a>.
+ */
+package org.apache.commons.lang3.text.translate;
diff --git a/src/main/java/org/apache/commons/lang3/time/CalendarUtils.java b/src/main/java/org/apache/commons/lang3/time/CalendarUtils.java
new file mode 100644
index 000000000..f2d986995
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/CalendarUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.time;
+
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Helps use {@link Calendar}s.
+ *
+ * @since 3.10
+ */
+public class CalendarUtils {
+
+ /**
+ * The singleton instance for {@link Calendar#getInstance()}.
+ */
+ public static final CalendarUtils INSTANCE = new CalendarUtils(Calendar.getInstance());
+
+ /**
+ * Gets a CalendarUtils using the default time zone and specified locale. The <code>CalendarUtils</code> returned is based on the current time in the
+ * default time zone with the given locale.
+ *
+ * @param locale the locale for the week data
+ * @return a Calendar.
+ */
+ static CalendarUtils getInstance(final Locale locale) {
+ return new CalendarUtils(Calendar.getInstance(locale), locale);
+ }
+
+ private final Calendar calendar;
+
+ private final Locale locale;
+
+ /**
+ * Creates an instance for the given Calendar.
+ *
+ * @param calendar A Calendar.
+ */
+ public CalendarUtils(final Calendar calendar) {
+ this(calendar, Locale.getDefault());
+ }
+
+ /**
+ * Creates an instance for the given Calendar.
+ *
+ * @param calendar A Calendar.
+ * @param locale A Locale.
+ */
+ CalendarUtils(final Calendar calendar, final Locale locale) {
+ this.calendar = Objects.requireNonNull(calendar, "calendar");
+ this.locale = Objects.requireNonNull(locale, "locale");
+ }
+ /**
+ * Gets the current day of month.
+ *
+ * @return the current day of month.
+ */
+ public int getDayOfMonth() {
+ return calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ /**
+ * Gets the current day of year.
+ *
+ * @return the current day of year.
+ * @since 3.13.0
+ */
+ public int getDayOfYear() {
+ return calendar.get(Calendar.DAY_OF_YEAR);
+ }
+
+ /**
+ * Gets the current month.
+ *
+ * @return the current month.
+ */
+ public int getMonth() {
+ return calendar.get(Calendar.MONTH);
+ }
+
+ /**
+ * Gets month names in the requested style.
+ * @param style Must be a valid {@link Calendar#getDisplayNames(int, int, Locale)} month style.
+ * @return Styled names of months
+ */
+ String[] getMonthDisplayNames(final int style) {
+ // Unfortunately standalone month names are not available in DateFormatSymbols,
+ // so we have to extract them.
+ final Map<String, Integer> displayNames = calendar.getDisplayNames(Calendar.MONTH, style, locale);
+ if (displayNames == null) {
+ return null;
+ }
+ final String[] monthNames = new String[displayNames.size()];
+ displayNames.forEach((k, v) -> monthNames[v] = k);
+ return monthNames;
+ }
+
+ /**
+ * Gets full standalone month names as used in "LLLL" date formatting.
+ * @return Long names of months
+ */
+ String[] getStandaloneLongMonthNames() {
+ return getMonthDisplayNames(Calendar.LONG_STANDALONE);
+ }
+
+ /**
+ * Gets short standalone month names as used in "LLLL" date formatting.
+ * @return Short names of months
+ */
+ String[] getStandaloneShortMonthNames() {
+ return getMonthDisplayNames(Calendar.SHORT_STANDALONE);
+ }
+
+ /**
+ * Gets the current year.
+ *
+ * @return the current year.
+ */
+ public int getYear() {
+ return calendar.get(Calendar.YEAR);
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java b/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java
new file mode 100644
index 000000000..1688c31e4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DateFormatUtils.java
@@ -0,0 +1,415 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Date and time formatting utilities and constants.
+ *
+ * <p>Formatting is performed using the thread-safe
+ * {@link org.apache.commons.lang3.time.FastDateFormat} class.</p>
+ *
+ * <p>Note that the JDK has a bug wherein calling Calendar.get(int) will
+ * override any previously called Calendar.clear() calls. See LANG-755.</p>
+ *
+ * <p>Note that when using capital YYYY instead of lowercase yyyy, the formatter
+ * will assume current year as week year is not supported. See {@link java.util.GregorianCalendar}
+ * Week Year section for an explanation on the difference between calendar and week years.</p>
+ *
+ * @since 2.0
+ */
+public class DateFormatUtils {
+
+ /**
+ * The UTC time zone (often referred to as GMT).
+ * This is private as it is mutable.
+ */
+ private static final TimeZone UTC_TIME_ZONE = FastTimeZone.getGmtTimeZone();
+
+ /**
+ * ISO 8601 formatter for date-time without time zone.
+ *
+ * <p>
+ * The format used is {@code yyyy-MM-dd'T'HH:mm:ss}. This format uses the
+ * default TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final FastDateFormat ISO_8601_EXTENDED_DATETIME_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
+
+ /**
+ * @deprecated - as of 4.0, ISO_DATETIME_FORMAT will be replaced by ISO_8601_EXTENDED_DATETIME_FORMAT.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_DATETIME_FORMAT = ISO_8601_EXTENDED_DATETIME_FORMAT;
+
+ /**
+ * ISO 8601 formatter for date-time with time zone.
+ *
+ * <p>
+ * The format used is {@code yyyy-MM-dd'T'HH:mm:ssZZ}. This format uses the
+ * default TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final FastDateFormat ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
+
+ /**
+ * @deprecated - as of 4.0, ISO_DATETIME_TIME_ZONE_FORMAT will be replaced by ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT;
+
+ /**
+ * ISO 8601 formatter for date without time zone.
+ *
+ * <p>
+ * The format used is {@code yyyy-MM-dd}. This format uses the
+ * default TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final FastDateFormat ISO_8601_EXTENDED_DATE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-dd");
+
+ /**
+ * @deprecated - as of 4.0, ISO_DATE_FORMAT will be replaced by ISO_8601_EXTENDED_DATE_FORMAT.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_DATE_FORMAT = ISO_8601_EXTENDED_DATE_FORMAT;
+
+ /**
+ * ISO 8601-like formatter for date with time zone.
+ *
+ * <p>
+ * The format used is {@code yyyy-MM-ddZZ}. This pattern does not comply
+ * with the formal ISO 8601 specification as the standard does not allow
+ * a time zone without a time. This format uses the default TimeZone in
+ * effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @deprecated - as of 4.0, ISO_DATE_TIME_ZONE_FORMAT will be removed.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_DATE_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("yyyy-MM-ddZZ");
+
+ /**
+ * Non-compliant formatter for time without time zone (ISO 8601 does not
+ * prefix 'T' for standalone time value).
+ *
+ * <p>
+ * The format used is {@code 'T'HH:mm:ss}. This format uses the default
+ * TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @deprecated - as of 4.0, ISO_TIME_FORMAT will be removed.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_TIME_FORMAT
+ = FastDateFormat.getInstance("'T'HH:mm:ss");
+
+ /**
+ * Non-compliant formatter for time with time zone (ISO 8601 does not
+ * prefix 'T' for standalone time value).
+ *
+ * <p>
+ * The format used is {@code 'T'HH:mm:ssZZ}. This format uses the default
+ * TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @deprecated - as of 4.0, ISO_TIME_TIME_ZONE_FORMAT will be removed.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_TIME_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("'T'HH:mm:ssZZ");
+
+ /**
+ * ISO 8601 formatter for time without time zone.
+ *
+ * <p>
+ * The format used is {@code HH:mm:ss}. This format uses the default
+ * TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final FastDateFormat ISO_8601_EXTENDED_TIME_FORMAT
+ = FastDateFormat.getInstance("HH:mm:ss");
+
+ /**
+ * @deprecated - as of 4.0, ISO_TIME_NO_T_FORMAT will be replaced by ISO_8601_EXTENDED_TIME_FORMAT.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_TIME_NO_T_FORMAT = ISO_8601_EXTENDED_TIME_FORMAT;
+
+ /**
+ * ISO 8601 formatter for time with time zone.
+ *
+ * <p>
+ * The format used is {@code HH:mm:ssZZ}. This format uses the default
+ * TimeZone in effect at the time of loading DateFormatUtils class.
+ * </p>
+ *
+ * @since 3.5
+ */
+ public static final FastDateFormat ISO_8601_EXTENDED_TIME_TIME_ZONE_FORMAT
+ = FastDateFormat.getInstance("HH:mm:ssZZ");
+
+ /**
+ * @deprecated - as of 4.0, ISO_TIME_NO_T_TIME_ZONE_FORMAT will be replaced by ISO_8601_EXTENDED_TIME_TIME_ZONE_FORMAT.
+ */
+ @Deprecated
+ public static final FastDateFormat ISO_TIME_NO_T_TIME_ZONE_FORMAT = ISO_8601_EXTENDED_TIME_TIME_ZONE_FORMAT;
+
+ /**
+ * SMTP (and probably other) date headers.
+ *
+ * <p>
+ * The format used is {@code EEE, dd MMM yyyy HH:mm:ss Z} in US locale.
+ * This format uses the default TimeZone in effect at the time of loading
+ * DateFormatUtils class.
+ * </p>
+ */
+ 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.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public DateFormatUtils() {
+ }
+
+ /**
+ * Formats a calendar into a specific pattern. The TimeZone from the calendar
+ * will be used for formatting.
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(final Calendar calendar, final String pattern) {
+ return format(calendar, pattern, getTimeZone(calendar), null);
+ }
+
+ /**
+ * Formats a calendar into a specific pattern in a locale. The TimeZone from the calendar
+ * will be used for formatting.
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(final Calendar calendar, final String pattern, final Locale locale) {
+ return format(calendar, pattern, getTimeZone(calendar), locale);
+ }
+
+ /**
+ * Formats a calendar into a specific pattern in a time zone.
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(final Calendar calendar, final String pattern, final TimeZone timeZone) {
+ return format(calendar, pattern, timeZone, null);
+ }
+
+ /**
+ * Formats a calendar into a specific pattern in a time zone and locale.
+ *
+ * @param calendar the calendar to format, not null
+ * @param pattern the pattern to use to format the calendar, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted calendar
+ * @see FastDateFormat#format(Calendar)
+ * @since 2.4
+ */
+ public static String format(final Calendar calendar, final String pattern, final TimeZone timeZone, final Locale locale) {
+ final FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);
+ return df.format(calendar);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String format(final Date date, final String pattern) {
+ return format(date, pattern, null, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a locale.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final Date date, final String pattern, final Locale locale) {
+ return format(date, pattern, null, locale);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a time zone.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final Date date, final String pattern, final TimeZone timeZone) {
+ return format(date, pattern, timeZone, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a time zone and locale.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final Date date, final String pattern, final TimeZone timeZone, final Locale locale) {
+ final FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);
+ return df.format(date);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern.
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String format(final long millis, final String pattern) {
+ return format(new Date(millis), pattern, null, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a locale.
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final long millis, final String pattern, final Locale locale) {
+ return format(new Date(millis), pattern, null, locale);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a time zone.
+ *
+ * @param millis the time expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final long millis, final String pattern, final TimeZone timeZone) {
+ return format(new Date(millis), pattern, timeZone, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern in a time zone and locale.
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param timeZone the time zone to use, may be {@code null}
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String format(final long millis, final String pattern, final TimeZone timeZone, final Locale locale) {
+ return format(new Date(millis), pattern, timeZone, locale);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern using the UTC time zone.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String formatUTC(final Date date, final String pattern) {
+ return format(date, pattern, UTC_TIME_ZONE, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern using the UTC time zone.
+ *
+ * @param date the date to format, not null
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String formatUTC(final Date date, final String pattern, final Locale locale) {
+ return format(date, pattern, UTC_TIME_ZONE, locale);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern using the UTC time zone.
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @return the formatted date
+ */
+ public static String formatUTC(final long millis, final String pattern) {
+ return format(new Date(millis), pattern, UTC_TIME_ZONE, null);
+ }
+
+ /**
+ * Formats a date/time into a specific pattern using the UTC time zone.
+ *
+ * @param millis the date to format expressed in milliseconds
+ * @param pattern the pattern to use to format the date, not null
+ * @param locale the locale to use, may be {@code null}
+ * @return the formatted date
+ */
+ public static String formatUTC(final long millis, final String pattern, final Locale locale) {
+ return format(new Date(millis), pattern, UTC_TIME_ZONE, locale);
+ }
+
+ private static TimeZone getTimeZone(final Calendar calendar) {
+ return calendar == null ? null : calendar.getTimeZone();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/DateParser.java b/src/main/java/org/apache/commons/lang3/time/DateParser.java
new file mode 100644
index 000000000..e57146d7d
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DateParser.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * DateParser is the "missing" interface for the parsing methods of
+ * {@link java.text.DateFormat}. You can obtain an object implementing this
+ * interface by using one of the FastDateFormat factory methods.
+ * <p>
+ * Warning: Since binary compatible methods may be added to this interface in any
+ * release, developers are not expected to implement this interface.
+ *
+ * @since 3.2
+ */
+public interface DateParser {
+
+ /**
+ * Equivalent to DateFormat.parse(String).
+ *
+ * See {@link java.text.DateFormat#parse(String)} for more information.
+ * @param source A {@link String} whose beginning should be parsed.
+ * @return A {@link Date} parsed from the string
+ * @throws ParseException if the beginning of the specified string cannot be parsed.
+ */
+ Date parse(String source) throws ParseException;
+
+ /**
+ * Equivalent to DateFormat.parse(String, ParsePosition).
+ *
+ * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information.
+ *
+ * @param source A {@link String}, part of which should be parsed.
+ * @param pos A {@link ParsePosition} object with index and error index information
+ * as described above.
+ * @return A {@link Date} parsed from the string. In case of error, returns null.
+ * @throws NullPointerException if text or pos is null.
+ */
+ Date parse(String source, ParsePosition pos);
+
+ /**
+ * 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.
+ *
+ * @since 3.5
+ */
+ 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.
+ *
+ * <p>
+ * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by
+ * the format pattern.
+ * </p>
+ *
+ * @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.
+ *
+ * @param source A {@link String} whose beginning should be parsed.
+ * @return a {@code java.util.Date} object
+ * @throws ParseException if the beginning of the specified string cannot be parsed.
+ * @see java.text.DateFormat#parseObject(String)
+ */
+ Object parseObject(String source) throws ParseException;
+
+ /**
+ * Parses a date/time string according to the given parse position.
+ *
+ * @param source A {@link String} whose beginning should be parsed.
+ * @param pos the parse position
+ * @return a {@code java.util.Date} object
+ * @see java.text.DateFormat#parseObject(String, ParsePosition)
+ */
+ Object parseObject(String source, ParsePosition pos);
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/DatePrinter.java b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
new file mode 100644
index 000000000..1ae6a01ce
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.FieldPosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * DatePrinter is the "missing" interface for the format methods of
+ * {@link java.text.DateFormat}. You can obtain an object implementing this
+ * interface by using one of the FastDateFormat factory methods.
+ * <p>
+ * Warning: Since binary compatible methods may be added to this interface in any
+ * release, developers are not expected to implement this interface.
+ *
+ * @since 3.2
+ */
+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.
+ * The TimeZone specified during the construction of the Parser will determine the TimeZone
+ * used in the formatted string.
+ *
+ * @param calendar the calendar to format.
+ * @return the formatted string
+ */
+ String format(Calendar calendar);
+
+ /**
+ * 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} 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);
+
+ /**
+ * Formats a {@link Calendar} object into the supplied {@link StringBuffer}.
+ * 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 calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ * @deprecated Use {{@link #format(Calendar, Appendable)}.
+ */
+ @Deprecated
+ StringBuffer format(Calendar calendar, StringBuffer buf);
+
+ /**
+ * 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 <B> the Appendable class type, usually StringBuilder or StringBuffer.
+ * @return the specified string buffer
+ * @since 3.5
+ */
+ <B extends Appendable> B format(long millis, B buf);
+
+ /**
+ * Formats a {@link Date} object into the
+ * supplied {@link Appendable} using a {@link GregorianCalendar}.
+ *
+ * @param date the date to format
+ * @param buf the buffer to format into
+ * @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
+ * @return the specified string buffer
+ * @since 3.5
+ */
+ <B extends Appendable> 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.
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
+ * @return the specified string buffer
+ * @since 3.5
+ */
+ <B extends Appendable> B format(Calendar calendar, B buf);
+
+
+ // Accessors
+ /**
+ * Gets the pattern used by this printer.
+ *
+ * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+ */
+ String getPattern();
+
+ /**
+ * Gets the time zone used by this printer.
+ *
+ * <p>This zone is always used for {@link Date} printing.</p>
+ *
+ * @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
new file mode 100644
index 000000000..383db9841
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
@@ -0,0 +1,1777 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.LocaleUtils;
+
+/**
+ * A suite of utilities surrounding the use of the
+ * {@link java.util.Calendar} and {@link java.util.Date} object.
+ *
+ * <p>DateUtils contains a lot of common methods considering manipulations
+ * of Dates or Calendars. Some methods require some extra explanation.
+ * The truncate, ceiling and round methods could be considered the Math.floor(),
+ * Math.ceil() or Math.round versions for dates
+ * This way date-fields will be ignored in bottom-up order.
+ * As a complement to these methods we've introduced some fragment-methods.
+ * With these methods the Date-fields will be ignored in top-down order.
+ * Since a date without a year is not a valid date, you have to decide in what
+ * kind of date-field you want your result, for instance milliseconds or days.
+ * </p>
+ * <p>
+ * Several methods are provided for adding to {@link Date} objects, of the form
+ * {@code addXXX(Date date, int amount)}. It is important to note these methods
+ * use a {@link Calendar} internally (with default time zone and locale) and may
+ * be affected by changes to daylight saving time (DST).
+ * </p>
+ *
+ * @since 2.0
+ */
+public class DateUtils {
+
+ /**
+ * Number of milliseconds in a standard second.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_SECOND = 1000;
+ /**
+ * Number of milliseconds in a standard minute.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+ /**
+ * Number of milliseconds in a standard hour.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
+ /**
+ * Number of milliseconds in a standard day.
+ * @since 2.1
+ */
+ public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
+
+ /**
+ * This is half a month, so this represents whether a date is in the top
+ * or bottom half of the month.
+ */
+ public static final int SEMI_MONTH = 1001;
+
+ private static final int[][] fields = {
+ {Calendar.MILLISECOND},
+ {Calendar.SECOND},
+ {Calendar.MINUTE},
+ {Calendar.HOUR_OF_DAY, Calendar.HOUR},
+ {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM
+ /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */
+ },
+ {Calendar.MONTH, SEMI_MONTH},
+ {Calendar.YEAR},
+ {Calendar.ERA}};
+
+ /**
+ * A week range, starting on Sunday.
+ */
+ public static final int RANGE_WEEK_SUNDAY = 1;
+ /**
+ * A week range, starting on Monday.
+ */
+ public static final int RANGE_WEEK_MONDAY = 2;
+ /**
+ * A week range, starting on the day focused.
+ */
+ public static final int RANGE_WEEK_RELATIVE = 3;
+ /**
+ * A week range, centered around the day focused.
+ */
+ public static final int RANGE_WEEK_CENTER = 4;
+ /**
+ * A month range, the week starting on Sunday.
+ */
+ public static final int RANGE_MONTH_SUNDAY = 5;
+ /**
+ * A month range, the week starting on Monday.
+ */
+ public static final int RANGE_MONTH_MONDAY = 6;
+
+ /**
+ * Calendar modification types.
+ */
+ private enum ModifyType {
+ /**
+ * Truncation.
+ */
+ TRUNCATE,
+
+ /**
+ * Rounding.
+ */
+ ROUND,
+
+ /**
+ * Ceiling.
+ */
+ 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);}.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public DateUtils() {
+ }
+
+ /**
+ * Checks if two date objects are on the same day ignoring time.
+ *
+ * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
+ * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
+ * </p>
+ *
+ * @param date1 the first date, not altered, not null
+ * @param date2 the second date, not altered, not null
+ * @return true if they represent the same day
+ * @throws 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 are on the same day ignoring time.
+ *
+ * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
+ * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
+ * </p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same day
+ * @throws 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 represent the same instant in time.
+ *
+ * <p>This method compares the long millisecond time of the two objects.</p>
+ *
+ * @param date1 the first date, not altered, not null
+ * @param date2 the second date, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws 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 instant in time.
+ *
+ * <p>This method compares the long millisecond time of the two objects.</p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws 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 calendar objects represent the same local time.
+ *
+ * <p>This method compares the values of the fields of the two objects.
+ * In addition, both calendars must be the same of the same type.</p>
+ *
+ * @param cal1 the first calendar, not altered, not null
+ * @param cal2 the second calendar, not altered, not null
+ * @return true if they represent the same millisecond instant
+ * @throws 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();
+ }
+
+ /**
+ * Parses a string representing a date by trying a variety of different parsers.
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser will be lenient toward the parsed date.
+ *
+ * @param str the date to parse, not null
+ * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
+ * @return the parsed date
+ * @throws 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.
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser will be lenient toward the parsed date.
+ *
+ * @param str the date to parse, not null
+ * @param 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 Date parseDate(final String str, final Locale locale, final String... parsePatterns) throws ParseException {
+ return parseDateWithLeniency(str, locale, parsePatterns, true);
+ }
+
+ /**
+ * Parses a string representing a date by trying a variety of different parsers.
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
+ *
+ * @param str the date to parse, not null
+ * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
+ * @return the parsed date
+ * @throws 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,
+ * using the default date format symbols for the given locale..
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
+ *
+ * @param str the date to parse, not null
+ * @param 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.
+ *
+ * <p>The parse will try each parse pattern in turn.
+ * A parse is only deemed successful if it parses the whole of the input string.
+ * If no parse patterns match, a ParseException is thrown.</p>
+ *
+ * @param 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);
+ }
+
+ /**
+ * 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 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);
+ }
+
+ /**
+ * Adds a number of days 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 addDays(final Date date, final int amount) {
+ return add(date, Calendar.DAY_OF_MONTH, amount);
+ }
+
+ /**
+ * Adds a number of hours 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 addHours(final Date date, final int amount) {
+ return add(date, Calendar.HOUR_OF_DAY, amount);
+ }
+
+ /**
+ * Adds a number of minutes 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 addMinutes(final Date date, final int amount) {
+ return add(date, Calendar.MINUTE, amount);
+ }
+
+ /**
+ * Adds a number of seconds 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 addSeconds(final Date date, final int amount) {
+ return add(date, Calendar.SECOND, 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 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.
+ *
+ * <p>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.</p>
+ *
+ * <p>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:
+ * </p>
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>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:
+ * </p>
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>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:
+ * </p>
+ * <ul>
+ * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
+ * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
+ * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * @param calendar 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 Calendar ceiling(final Calendar calendar, final int field) {
+ Objects.requireNonNull(calendar, "calendar");
+ return modify((Calendar) calendar.clone(), field, ModifyType.CEILING);
+ }
+
+ /**
+ * Gets a date ceiling, leaving the field specified as the most
+ * significant field.
+ *
+ * <p>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.</p>
+ *
+ * @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 ceil 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
+ * @since 2.5
+ */
+ public static Date ceiling(final Object date, final int field) {
+ Objects.requireNonNull(date, "date");
+ if (date instanceof Date) {
+ return ceiling((Date) date, field);
+ }
+ if (date instanceof Calendar) {
+ return ceiling((Calendar) date, field).getTime();
+ }
+ throw new ClassCastException("Could not find ceiling of for type: " + date.getClass());
+ }
+
+ /**
+ * Internal calculation method.
+ *
+ * @param val the calendar, not null
+ * @param field the field constant
+ * @param modType type to truncate, round or ceiling
+ * @return the given calendar
+ * @throws ArithmeticException if the year is over 280 million
+ */
+ private static Calendar modify(final Calendar val, final int field, final ModifyType modType) {
+ if (val.get(Calendar.YEAR) > 280000000) {
+ throw new ArithmeticException("Calendar value too large for accurate calculations");
+ }
+
+ if (field == Calendar.MILLISECOND) {
+ return val;
+ }
+
+ // Fix for LANG-59 START
+ // see https://issues.apache.org/jira/browse/LANG-59
+ //
+ // Manually truncate milliseconds, seconds and minutes, rather than using
+ // Calendar methods.
+
+ final Date date = val.getTime();
+ long time = date.getTime();
+ boolean done = false;
+
+ // truncate milliseconds
+ final int millisecs = val.get(Calendar.MILLISECOND);
+ if (ModifyType.TRUNCATE == modType || millisecs < 500) {
+ time = time - millisecs;
+ }
+ if (field == Calendar.SECOND) {
+ done = true;
+ }
+
+ // truncate seconds
+ final int seconds = val.get(Calendar.SECOND);
+ if (!done && (ModifyType.TRUNCATE == modType || seconds < 30)) {
+ time = time - (seconds * 1000L);
+ }
+ if (field == Calendar.MINUTE) {
+ done = true;
+ }
+
+ // truncate minutes
+ final int minutes = val.get(Calendar.MINUTE);
+ if (!done && (ModifyType.TRUNCATE == modType || minutes < 30)) {
+ time = time - (minutes * 60000L);
+ }
+
+ // reset time
+ if (date.getTime() != time) {
+ date.setTime(time);
+ val.setTime(date);
+ }
+ // Fix for LANG-59 END
+
+ boolean roundUp = false;
+ for (final int[] aField : fields) {
+ for (final int element : aField) {
+ if (element == field) {
+ //This is our field... we stop looping
+ if (modType == ModifyType.CEILING || modType == ModifyType.ROUND && roundUp) {
+ if (field == SEMI_MONTH) {
+ //This is a special case that's hard to generalize
+ //If the date is 1, we round up to 16, otherwise
+ // we subtract 15 days and add 1 month
+ if (val.get(Calendar.DATE) == 1) {
+ val.add(Calendar.DATE, 15);
+ } else {
+ val.add(Calendar.DATE, -15);
+ val.add(Calendar.MONTH, 1);
+ }
+ // Fix for LANG-440 START
+ } else if (field == Calendar.AM_PM) {
+ // This is a special case
+ // If the time is 0, we round up to 12, otherwise
+ // we subtract 12 hours and add 1 day
+ if (val.get(Calendar.HOUR_OF_DAY) == 0) {
+ val.add(Calendar.HOUR_OF_DAY, 12);
+ } else {
+ val.add(Calendar.HOUR_OF_DAY, -12);
+ val.add(Calendar.DATE, 1);
+ }
+ // Fix for LANG-440 END
+ } else {
+ //We need at add one to this field since the
+ // last number causes us to round up
+ val.add(aField[0], 1);
+ }
+ }
+ return val;
+ }
+ }
+ //We have various fields that are not easy roundings
+ int offset = 0;
+ boolean offsetSet = false;
+ //These are special types of fields that require different rounding rules
+ switch (field) {
+ case SEMI_MONTH:
+ if (aField[0] == Calendar.DATE) {
+ //If we're going to drop the DATE field's value,
+ // we want to do this our own way.
+ //We need to subtract 1 since the date has a minimum of 1
+ offset = val.get(Calendar.DATE) - 1;
+ //If we're above 15 days adjustment, that means we're in the
+ // bottom half of the month and should stay accordingly.
+ if (offset >= 15) {
+ offset -= 15;
+ }
+ //Record whether we're in the top or bottom half of that range
+ roundUp = offset > 7;
+ offsetSet = true;
+ }
+ break;
+ case Calendar.AM_PM:
+ if (aField[0] == Calendar.HOUR_OF_DAY) {
+ //If we're going to drop the HOUR field's value,
+ // we want to do this our own way.
+ offset = val.get(Calendar.HOUR_OF_DAY);
+ if (offset >= 12) {
+ offset -= 12;
+ }
+ roundUp = offset >= 6;
+ offsetSet = true;
+ }
+ break;
+ default:
+ break;
+ }
+ if (!offsetSet) {
+ final int min = val.getActualMinimum(aField[0]);
+ final int max = val.getActualMaximum(aField[0]);
+ //Calculate the offset from the minimum allowed value
+ offset = val.get(aField[0]) - min;
+ //Set roundUp if this is more than half way between the minimum and maximum
+ roundUp = offset > ((max - min) / 2);
+ }
+ //We need to remove this field
+ if (offset != 0) {
+ val.set(aField[0], val.get(aField[0]) - offset);
+ }
+ }
+ throw new IllegalArgumentException("The field " + field + " is not supported");
+ }
+
+ /**
+ * Constructs an {@link Iterator} over each day in a date
+ * range defined by a focus date and range style.
+ *
+ * <p>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.</p>
+ *
+ * <p>This method provides an iterator that returns Calendar objects.
+ * The days are progressed using {@link Calendar#add(int, int)}.</p>
+ *
+ * @param focus the date to work with, not null
+ * @param rangeStyle the style constant to use. Must be one of
+ * {@link DateUtils#RANGE_MONTH_SUNDAY},
+ * {@link DateUtils#RANGE_MONTH_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_SUNDAY},
+ * {@link DateUtils#RANGE_WEEK_MONDAY},
+ * {@link DateUtils#RANGE_WEEK_RELATIVE},
+ * {@link DateUtils#RANGE_WEEK_CENTER}
+ * @return the date iterator, not null, not null
+ * @throws NullPointerException if the date is {@code null}
+ * @throws IllegalArgumentException if the rangeStyle is invalid
+ */
+ public static Iterator<Calendar> 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.
+ *
+ * <p>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.</p>
+ *
+ * <p>This method provides an iterator that returns Calendar objects.
+ * The days are progressed using {@link Calendar#add(int, int)}.</p>
+ *
+ * @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<Calendar> 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.
+ *
+ * <p>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.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * Returns the number of milliseconds within the
+ * fragment. All date fields greater than the fragment will be ignored.
+ *
+ * <p>Asking the milliseconds of any date will only return the number of milliseconds
+ * of the current second (resulting in a number between 0 and 999). This
+ * method will retrieve the number of milliseconds for any fragment.
+ * For example, if you want to calculate the number of milliseconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all milliseconds of the past hour(s), minutes(s) and second(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 (10*1000 + 538)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in milliseconds)</li>
+ * </ul>
+ *
+ * @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 seconds within the
+ * fragment. All date fields greater than the fragment will be ignored.
+ *
+ * <p>Asking the seconds of any date will only return the number of seconds
+ * of the current minute (resulting in a number between 0 and 59). This
+ * method will retrieve the number of seconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s) and minutes(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to deprecated date.getSeconds())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to deprecated date.getSeconds())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110
+ * (7*3600 + 15*60 + 10)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in seconds)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the minutes of any date will only return the number of minutes
+ * of the current hour (resulting in a number between 0 and 59). This
+ * method will retrieve the number of minutes for any fragment.
+ * For example, if you want to calculate the number of minutes past this month,
+ * your fragment is Calendar.MONTH. The result will be all minutes of the
+ * past day(s) and hour(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MINUTE field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to deprecated date.getMinutes())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to deprecated date.getMinutes())</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in minutes)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the hours of any date will only return the number of hours
+ * of the current day (resulting in a number between 0 and 23). This
+ * method will retrieve the number of hours for any fragment.
+ * For example, if you want to calculate the number of hours past this month,
+ * your fragment is Calendar.MONTH. The result will be all hours of the
+ * past day(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a HOUR field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to deprecated date.getHours())</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to deprecated date.getHours())</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in hours)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the days of any date will only return the number of days
+ * of the current month (resulting in a number between 1 and 31). This
+ * method will retrieve the number of days for any fragment.
+ * For example, if you want to calculate the number of days past this year,
+ * your fragment is Calendar.YEAR. The result will be all days of the
+ * past month(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a DAY field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to deprecated date.getDay())</li>
+ * <li>February 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to deprecated date.getDay())</li>
+ * <li>January 28, 2008 with Calendar.YEAR as fragment will return 28</li>
+ * <li>February 28, 2008 with Calendar.YEAR as fragment will return 59</li>
+ * <li>January 28, 2008 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in days)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the milliseconds of any date will only return the number of milliseconds
+ * of the current second (resulting in a number between 0 and 999). This
+ * method will retrieve the number of milliseconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s), minutes(s) and second(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MILLISECOND field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
+ * (equivalent to calendar.get(Calendar.MILLISECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538
+ * (equivalent to calendar.get(Calendar.MILLISECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538
+ * (10*1000 + 538)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in milliseconds)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the seconds of any date will only return the number of seconds
+ * of the current minute (resulting in a number between 0 and 59). This
+ * method will retrieve the number of seconds for any fragment.
+ * For example, if you want to calculate the number of seconds past today,
+ * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
+ * be all seconds of the past hour(s) and minutes(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a SECOND field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to calendar.get(Calendar.SECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
+ * (equivalent to calendar.get(Calendar.SECOND))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110
+ * (7*3600 + 15*60 + 10)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in seconds)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the minutes of any date will only return the number of minutes
+ * of the current hour (resulting in a number between 0 and 59). This
+ * method will retrieve the number of minutes for any fragment.
+ * For example, if you want to calculate the number of minutes past this month,
+ * your fragment is Calendar.MONTH. The result will be all minutes of the
+ * past day(s) and hour(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a MINUTE field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to calendar.get(Calendar.MINUTES))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
+ * (equivalent to calendar.get(Calendar.MINUTES))</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in minutes)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the hours of any date will only return the number of hours
+ * of the current day (resulting in a number between 0 and 23). This
+ * method will retrieve the number of hours for any fragment.
+ * For example, if you want to calculate the number of hours past this month,
+ * your fragment is Calendar.MONTH. The result will be all hours of the
+ * past day(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a HOUR field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to calendar.get(Calendar.HOUR_OF_DAY))</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 7
+ * (equivalent to calendar.get(Calendar.HOUR_OF_DAY))</li>
+ * <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 7</li>
+ * <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 127 (5*24 + 7)</li>
+ * <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in hours)</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Asking the days of any date will only return the number of days
+ * of the current month (resulting in a number between 1 and 31). This
+ * method will retrieve the number of days for any fragment.
+ * For example, if you want to calculate the number of days past this year,
+ * your fragment is Calendar.YEAR. The result will be all days of the
+ * past month(s).</p>
+ *
+ * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both
+ * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY,
+ * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
+ * A fragment less than or equal to a DAY field will return 0.</p>
+ *
+ * <ul>
+ * <li>January 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))</li>
+ * <li>February 28, 2008 with Calendar.MONTH as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_MONTH))</li>
+ * <li>January 28, 2008 with Calendar.YEAR as fragment will return 28
+ * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))</li>
+ * <li>February 28, 2008 with Calendar.YEAR as fragment will return 59
+ * (equivalent to calendar.get(Calendar.DAY_OF_YEAR))</li>
+ * <li>January 28, 2008 with Calendar.MILLISECOND as fragment will return 0
+ * (a millisecond cannot be split in days)</li>
+ * </ul>
+ *
+ * @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) {
+ validateDateNotNull(date);
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return getFragment(calendar, fragment, unit);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Determines how two calendars compare 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 a negative integer, zero, or a positive integer as the first
+ * calendar is less than, equal to, or greater than the second.
+ * @throws NullPointerException if any argument is {@code null}
+ * @see #truncate(Calendar, int)
+ * @see #truncatedCompareTo(Date, Date, int)
+ * @since 3.0
+ */
+ public static int truncatedCompareTo(final Calendar cal1, final Calendar cal2, final int field) {
+ final Calendar truncatedCal1 = truncate(cal1, field);
+ final Calendar truncatedCal2 = truncate(cal2, field);
+ return truncatedCal1.compareTo(truncatedCal2);
+ }
+
+ /**
+ * Determines how two dates compare up to no more than the specified
+ * most significant field.
+ *
+ * @param date1 the first date, not {@code null}
+ * @param date2 the second date, not {@code null}
+ * @param field the field from {@link Calendar}
+ * @return a negative integer, zero, or a positive integer as the first
+ * date is less than, equal to, or greater than the second.
+ * @throws NullPointerException if any argument is {@code null}
+ * @see #truncate(Calendar, int)
+ * @see #truncatedCompareTo(Date, Date, int)
+ * @since 3.0
+ */
+ public static int truncatedCompareTo(final Date date1, final Date date2, final int field) {
+ final Date truncatedDate1 = truncate(date1, field);
+ final Date truncatedDate2 = truncate(date2, field);
+ return truncatedDate1.compareTo(truncatedDate2);
+ }
+
+ /**
+ * @param date Date to validate.
+ * @throws NullPointerException if {@code date == null}
+ */
+ private static void validateDateNotNull(final Date date) {
+ Objects.requireNonNull(date, "date");
+ }
+
+ /**
+ * Date iterator.
+ */
+ static class DateIterator implements Iterator<Calendar> {
+ private final Calendar endFinal;
+ private final Calendar spot;
+
+ /**
+ * Constructs a DateIterator that ranges from one date to another.
+ *
+ * @param startFinal start date (inclusive)
+ * @param endFinal end date (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();
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
new file mode 100644
index 000000000..d1b046568
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -0,0 +1,685 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Duration formatting utilities and constants. The following table describes the tokens
+ * used in the pattern language for formatting.
+ * <table border="1">
+ * <caption>Pattern Tokens</caption>
+ * <tr><th>character</th><th>duration element</th></tr>
+ * <tr><td>y</td><td>years</td></tr>
+ * <tr><td>M</td><td>months</td></tr>
+ * <tr><td>d</td><td>days</td></tr>
+ * <tr><td>H</td><td>hours</td></tr>
+ * <tr><td>m</td><td>minutes</td></tr>
+ * <tr><td>s</td><td>seconds</td></tr>
+ * <tr><td>S</td><td>milliseconds</td></tr>
+ * <tr><td>'text'</td><td>arbitrary text content</td></tr>
+ * </table>
+ *
+ * <b>Note: It's not currently possible to include a single-quote in a format.</b>
+ * <br>
+ * Token values are printed using decimal digits.
+ * A token character can be repeated to ensure that the field occupies a certain minimum
+ * size. Values will be left-padded with 0 unless padding is disabled in the method invocation.
+ * @since 2.1
+ */
+public class DurationFormatUtils {
+
+ /**
+ * DurationFormatUtils instances should NOT be constructed in standard programming.
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean instance
+ * to operate.</p>
+ */
+ public DurationFormatUtils() {
+ }
+
+ /**
+ * Pattern used with {@link FastDateFormat} and {@link SimpleDateFormat}
+ * for the ISO 8601 period format used in durations.
+ *
+ * @see org.apache.commons.lang3.time.FastDateFormat
+ * @see java.text.SimpleDateFormat
+ */
+ public static final String ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.SSS'S'";
+
+ /**
+ * Formats the time gap as a string.
+ *
+ * <p>The format used is ISO 8601-like: {@code HH:mm:ss.SSS}.</p>
+ *
+ * @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.
+ *
+ * <p>The format used is the ISO 8601 period format.</p>
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * ISO format pattern, such as P7D6TH5M4.321S.</p>
+ *
+ * @param durationMillis the duration to format
+ * @return the formatted duration, not null
+ * @throws IllegalArgumentException if durationMillis is negative
+ */
+ public static String formatDurationISO(final long durationMillis) {
+ return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false);
+ }
+
+ /**
+ * Formats the time gap as a string, using the specified format, and padding with zeros.
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the duration to format
+ * @param format the way in which to format the duration, not null
+ * @return the formatted duration, not null
+ * @throws IllegalArgumentException if durationMillis is negative
+ */
+ public static String formatDuration(final long durationMillis, final String format) {
+ return formatDuration(durationMillis, format, true);
+ }
+
+ /**
+ * Formats the time gap as a string, using the specified format.
+ * Padding the left-hand side of numbers with zeroes is optional.
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the duration to format
+ * @param format the way in which to format the duration, not null
+ * @param padWithZeros whether to pad the left-hand side of numbers with 0's
+ * @return the formatted duration, not null
+ * @throws IllegalArgumentException if durationMillis is negative
+ */
+ public static String formatDuration(final long durationMillis, final String format, final boolean padWithZeros) {
+ Validate.inclusiveBetween(0, Long.MAX_VALUE, durationMillis, "durationMillis must not be negative");
+
+ final Token[] tokens = lexx(format);
+
+ long days = 0;
+ long hours = 0;
+ long minutes = 0;
+ long seconds = 0;
+ long milliseconds = durationMillis;
+
+ if (Token.containsTokenWithValue(tokens, d)) {
+ days = milliseconds / DateUtils.MILLIS_PER_DAY;
+ milliseconds = milliseconds - (days * DateUtils.MILLIS_PER_DAY);
+ }
+ if (Token.containsTokenWithValue(tokens, H)) {
+ hours = milliseconds / DateUtils.MILLIS_PER_HOUR;
+ milliseconds = milliseconds - (hours * DateUtils.MILLIS_PER_HOUR);
+ }
+ if (Token.containsTokenWithValue(tokens, m)) {
+ minutes = milliseconds / DateUtils.MILLIS_PER_MINUTE;
+ milliseconds = milliseconds - (minutes * DateUtils.MILLIS_PER_MINUTE);
+ }
+ if (Token.containsTokenWithValue(tokens, s)) {
+ seconds = milliseconds / DateUtils.MILLIS_PER_SECOND;
+ milliseconds = milliseconds - (seconds * DateUtils.MILLIS_PER_SECOND);
+ }
+
+ return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros);
+ }
+
+ /**
+ * Formats an elapsed time into a pluralization correct string.
+ *
+ * <p>This method formats durations using the days and lower fields of the
+ * format pattern. Months and larger are not used.</p>
+ *
+ * @param durationMillis the elapsed time to report in milliseconds
+ * @param suppressLeadingZeroElements suppresses leading 0 elements
+ * @param suppressTrailingZeroElements suppresses trailing 0 elements
+ * @return the formatted text in days/hours/minutes/seconds, not null
+ * @throws IllegalArgumentException if durationMillis is negative
+ */
+ public static String formatDurationWords(
+ final long durationMillis,
+ final boolean suppressLeadingZeroElements,
+ final boolean suppressTrailingZeroElements) {
+
+ // This method is generally replaceable by the format method, but
+ // there are a series of tweaks and special cases that require
+ // trickery to replicate.
+ String duration = formatDuration(durationMillis, "d' days 'H' hours 'm' minutes 's' seconds'");
+ if (suppressLeadingZeroElements) {
+ // this is a temporary marker on the front. Like ^ in regexp.
+ duration = " " + duration;
+ String tmp = StringUtils.replaceOnce(duration, " 0 days", StringUtils.EMPTY);
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 hours", StringUtils.EMPTY);
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 minutes", StringUtils.EMPTY);
+ duration = tmp;
+ if (tmp.length() != duration.length()) {
+ duration = StringUtils.replaceOnce(tmp, " 0 seconds", StringUtils.EMPTY);
+ }
+ }
+ }
+ if (!duration.isEmpty()) {
+ // strip the space off again
+ duration = duration.substring(1);
+ }
+ }
+ if (suppressTrailingZeroElements) {
+ String tmp = StringUtils.replaceOnce(duration, " 0 seconds", StringUtils.EMPTY);
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 minutes", StringUtils.EMPTY);
+ if (tmp.length() != duration.length()) {
+ duration = tmp;
+ tmp = StringUtils.replaceOnce(duration, " 0 hours", StringUtils.EMPTY);
+ if (tmp.length() != duration.length()) {
+ duration = StringUtils.replaceOnce(tmp, " 0 days", StringUtils.EMPTY);
+ }
+ }
+ }
+ }
+ // handle plurals
+ duration = " " + duration;
+ duration = StringUtils.replaceOnce(duration, " 1 seconds", " 1 second");
+ duration = StringUtils.replaceOnce(duration, " 1 minutes", " 1 minute");
+ duration = StringUtils.replaceOnce(duration, " 1 hours", " 1 hour");
+ duration = StringUtils.replaceOnce(duration, " 1 days", " 1 day");
+ return duration.trim();
+ }
+
+ /**
+ * Formats the time gap as a string.
+ *
+ * <p>The format used is the ISO 8601 period format.</p>
+ *
+ * @param startMillis the start of the duration to format
+ * @param endMillis the end of the duration to format
+ * @return the formatted duration, not null
+ * @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.
+ *
+ * @param startMillis the start of the duration
+ * @param endMillis the end of the duration
+ * @param format the way in which to format the duration, not null
+ * @return the formatted duration, not null
+ * @throws IllegalArgumentException if startMillis is greater than endMillis
+ */
+ public static String formatPeriod(final long startMillis, final long endMillis, final String format) {
+ return formatPeriod(startMillis, endMillis, format, true, TimeZone.getDefault());
+ }
+
+ /**
+ * <p>Formats the time gap as a string, using the specified format.
+ * Padding the left-hand side of numbers with zeroes is optional and
+ * the time zone may be specified.
+ *
+ * <p>When calculating the difference between months/days, it chooses to
+ * calculate months first. So when working out the number of months and
+ * days between January 15th and March 10th, it choose 1 month and
+ * 23 days gained by choosing January-&gt;February = 1 month and then
+ * calculating days forwards, and not the 1 month and 26 days gained by
+ * choosing March -&gt; February = 1 month and then calculating days
+ * backwards.</p>
+ *
+ * <p>For more control, the <a href="https://www.joda.org/joda-time/">Joda-Time</a>
+ * library is recommended.</p>
+ *
+ * @param startMillis the start of the duration
+ * @param endMillis the end of the duration
+ * @param format the way in which to format the duration, not null
+ * @param padWithZeros whether to pad the left-hand side of numbers with 0's
+ * @param timezone the millis are defined in
+ * @return the formatted duration, not null
+ * @throws IllegalArgumentException if startMillis is greater than endMillis
+ */
+ public static String formatPeriod(final long startMillis, final long endMillis, final String format, final boolean padWithZeros,
+ final TimeZone timezone) {
+ Validate.isTrue(startMillis <= endMillis, "startMillis must not be greater than endMillis");
+
+
+ // Used to optimise for differences under 28 days and
+ // called formatDuration(millis, format); however this did not work
+ // over leap years.
+ // TODO: Compare performance to see if anything was lost by
+ // losing this optimisation.
+
+ final Token[] tokens = lexx(format);
+
+ // time zones get funky around 0, so normalizing everything to GMT
+ // stops the hours being off
+ final Calendar start = Calendar.getInstance(timezone);
+ start.setTime(new Date(startMillis));
+ final Calendar end = Calendar.getInstance(timezone);
+ end.setTime(new Date(endMillis));
+
+ // initial estimates
+ int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND);
+ int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
+ int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
+ int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY);
+ int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH);
+ int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
+ int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
+
+ // each initial estimate is adjusted in case it is under 0
+ while (milliseconds < 0) {
+ milliseconds += 1000;
+ seconds -= 1;
+ }
+ while (seconds < 0) {
+ seconds += 60;
+ minutes -= 1;
+ }
+ while (minutes < 0) {
+ minutes += 60;
+ hours -= 1;
+ }
+ while (hours < 0) {
+ hours += 24;
+ days -= 1;
+ }
+
+ if (Token.containsTokenWithValue(tokens, M)) {
+ while (days < 0) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ months -= 1;
+ start.add(Calendar.MONTH, 1);
+ }
+
+ while (months < 0) {
+ months += 12;
+ years -= 1;
+ }
+
+ if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
+ while (years != 0) {
+ months += 12 * years;
+ years = 0;
+ }
+ }
+ } else {
+ // there are no M's in the format string
+
+ if (!Token.containsTokenWithValue(tokens, y)) {
+ int target = end.get(Calendar.YEAR);
+ if (months < 0) {
+ // target is end-year -1
+ target -= 1;
+ }
+
+ while (start.get(Calendar.YEAR) != target) {
+ days += start.getActualMaximum(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR);
+
+ // Not sure I grok why this is needed, but the brutal tests show it is
+ if (start instanceof GregorianCalendar &&
+ start.get(Calendar.MONTH) == Calendar.FEBRUARY &&
+ start.get(Calendar.DAY_OF_MONTH) == 29) {
+ days += 1;
+ }
+
+ start.add(Calendar.YEAR, 1);
+
+ days += start.get(Calendar.DAY_OF_YEAR);
+ }
+
+ years = 0;
+ }
+
+ while (start.get(Calendar.MONTH) != end.get(Calendar.MONTH)) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ start.add(Calendar.MONTH, 1);
+ }
+
+ months = 0;
+
+ while (days < 0) {
+ days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+ months -= 1;
+ start.add(Calendar.MONTH, 1);
+ }
+
+ }
+
+ // The rest of this code adds in values that
+ // aren't requested. This allows the user to ask for the
+ // number of months and get the real count and not just 0->11.
+
+ if (!Token.containsTokenWithValue(tokens, d)) {
+ hours += 24 * days;
+ days = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, H)) {
+ minutes += 60 * hours;
+ hours = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, m)) {
+ seconds += 60 * minutes;
+ minutes = 0;
+ }
+ if (!Token.containsTokenWithValue(tokens, s)) {
+ milliseconds += 1000 * seconds;
+ seconds = 0;
+ }
+
+ return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
+ }
+
+ /**
+ * The internal method to do the formatting.
+ *
+ * @param tokens the tokens
+ * @param years the number of years
+ * @param months the number of months
+ * @param days the number of days
+ * @param hours the number of hours
+ * @param minutes the number of minutes
+ * @param seconds the number of seconds
+ * @param milliseconds the number of millis
+ * @param padWithZeros whether to pad
+ * @return the formatted string
+ */
+ static String format(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;
+ for (final Token token : tokens) {
+ final Object value = token.getValue();
+ final int count = token.getCount();
+ if (value instanceof StringBuilder) {
+ buffer.append(value.toString());
+ } else if (value.equals(y)) {
+ buffer.append(paddedValue(years, padWithZeros, count));
+ lastOutputSeconds = false;
+ } else if (value.equals(M)) {
+ buffer.append(paddedValue(months, padWithZeros, count));
+ lastOutputSeconds = false;
+ } else if (value.equals(d)) {
+ buffer.append(paddedValue(days, padWithZeros, count));
+ lastOutputSeconds = false;
+ } else if (value.equals(H)) {
+ buffer.append(paddedValue(hours, padWithZeros, count));
+ lastOutputSeconds = false;
+ } else if (value.equals(m)) {
+ buffer.append(paddedValue(minutes, padWithZeros, count));
+ lastOutputSeconds = false;
+ } else if (value.equals(s)) {
+ buffer.append(paddedValue(seconds, padWithZeros, count));
+ lastOutputSeconds = true;
+ } else if (value.equals(S)) {
+ 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;
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ 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
+ *
+ * @param format the format to parse, not null
+ * @return array of Token[]
+ */
+ static Token[] lexx(final String format) {
+ final ArrayList<Token> list = new ArrayList<>(format.length());
+
+ boolean inLiteral = false;
+ // Although the buffer is stored in a Token, the Tokens are only
+ // used internally, so cannot be accessed by other threads
+ StringBuilder buffer = null;
+ Token previous = null;
+ for (int i = 0; i < format.length(); i++) {
+ final char ch = format.charAt(i);
+ if (inLiteral && ch != '\'') {
+ buffer.append(ch); // buffer can't be null if inLiteral is true
+ continue;
+ }
+ String value = null;
+ switch (ch) {
+ // TODO: Need to handle escaping of '
+ case '\'':
+ if (inLiteral) {
+ buffer = null;
+ inLiteral = false;
+ } else {
+ buffer = new StringBuilder();
+ list.add(new Token(buffer));
+ inLiteral = true;
+ }
+ break;
+ case 'y':
+ value = y;
+ break;
+ case 'M':
+ value = M;
+ break;
+ case 'd':
+ value = d;
+ break;
+ case 'H':
+ value = H;
+ break;
+ case 'm':
+ value = m;
+ break;
+ case 's':
+ value = s;
+ break;
+ case 'S':
+ value = S;
+ break;
+ default:
+ if (buffer == null) {
+ buffer = new StringBuilder();
+ list.add(new Token(buffer));
+ }
+ buffer.append(ch);
+ }
+
+ if (value != null) {
+ if (previous != null && previous.getValue().equals(value)) {
+ previous.increment();
+ } else {
+ final Token token = new Token(value);
+ list.add(token);
+ previous = token;
+ }
+ buffer = null;
+ }
+ }
+ if (inLiteral) { // i.e. we have not found the end of the literal
+ throw new IllegalArgumentException("Unmatched quote in format: " + format);
+ }
+ return list.toArray(Token.EMPTY_ARRAY);
+ }
+
+ /**
+ * Element that is parsed from the format pattern.
+ */
+ 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;
+
+ /**
+ * Wraps a token around a value. A value would be something like a 'Y'.
+ *
+ * @param value to wrap
+ */
+ Token(final Object value) {
+ this.value = value;
+ this.count = 1;
+ }
+
+ /**
+ * Wraps a token around a repeated number of a value, for example it would
+ * store 'yyyy' as a value for y and a count of 4.
+ *
+ * @param value to wrap
+ * @param count to wrap
+ */
+ Token(final Object value, final int count) {
+ this.value = value;
+ this.count = count;
+ }
+
+ /**
+ * Adds another one of the value
+ */
+ void increment() {
+ count++;
+ }
+
+ /**
+ * Gets the current number of values represented
+ *
+ * @return int number of values represented
+ */
+ int getCount() {
+ return count;
+ }
+
+ /**
+ * Gets the particular value this token represents.
+ *
+ * @return Object value
+ */
+ Object getValue() {
+ return value;
+ }
+
+ /**
+ * Supports equality of this Token to another Token.
+ *
+ * @param obj2 Object to consider equality of
+ * @return boolean {@code true} 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);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/DurationUtils.java b/src/main/java/org/apache/commons/lang3/time/DurationUtils.java
new file mode 100644
index 000000000..2a6d7ebd7
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/DurationUtils.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.time;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.LongRange;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.function.FailableBiConsumer;
+import org.apache.commons.lang3.function.FailableConsumer;
+import org.apache.commons.lang3.function.FailableRunnable;
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * Utilities for {@link Duration}.
+ *
+ * @since 3.12.0
+ */
+public class DurationUtils {
+
+ /**
+ * An Integer Range that accepts Longs.
+ */
+ static final LongRange LONG_TO_INT_RANGE = LongRange.of(NumberUtils.LONG_INT_MIN_VALUE, NumberUtils.LONG_INT_MAX_VALUE);
+
+ /**
+ * Accepts the function with the duration as a long milliseconds and int nanoseconds.
+ *
+ * @param <T> The function exception.
+ * @param consumer Accepting function.
+ * @param duration The duration to pick apart.
+ * @throws T See the function signature.
+ */
+ @SuppressWarnings("boxing") // boxing unavoidable
+ public static <T extends Throwable> void accept(final FailableBiConsumer<Long, Integer, T> consumer, final Duration duration)
+ throws T {
+ if (consumer != null && duration != null) {
+ consumer.accept(duration.toMillis(), getNanosOfMilli(duration));
+ }
+ }
+
+ /**
+ * Gets the nanosecond part of a Duration converted to milliseconds.
+ * <p>
+ * Handy when calling an API that takes a long of milliseconds and an int of nanoseconds. For example,
+ * {@link Object#wait(long, int)} and {@link Thread#sleep(long, int)}.
+ * </p>
+ * <p>
+ * Note that is this different from {@link Duration#getNano()} because a duration are seconds and nanoseconds.
+ * </p>
+ *
+ * @param duration The duration to query.
+ * @return nanoseconds between 0 and 999,999.
+ * @deprecated Use {@link #getNanosOfMilli(Duration)}.
+ */
+ @Deprecated
+ public static int getNanosOfMiili(final Duration duration) {
+ return getNanosOfMilli(duration);
+ }
+
+ /**
+ * Gets the nanosecond part of a Duration converted to milliseconds.
+ * <p>
+ * Handy when calling an API that takes a long of milliseconds and an int of nanoseconds. For example,
+ * {@link Object#wait(long, int)} and {@link Thread#sleep(long, int)}.
+ * </p>
+ * <p>
+ * Note that is this different from {@link Duration#getNano()} because a duration are seconds and nanoseconds.
+ * </p>
+ *
+ * @param duration The duration to query.
+ * @return nanoseconds between 0 and 999,999.
+ * @since 3.13.0
+ */
+ public static int getNanosOfMilli(final Duration duration) {
+ return zeroIfNull(duration).getNano() % 1_000_000;
+ }
+
+ /**
+ * Tests whether the given Duration is positive (&gt;0).
+ *
+ * @param duration the value to test
+ * @return whether the given Duration is positive (&gt;0).
+ */
+ public static boolean isPositive(final Duration duration) {
+ return !duration.isNegative() && !duration.isZero();
+ }
+
+ /**
+ * Runs the lambda and returns the duration of its execution.
+ *
+ * @param <E> The type of exception throw by the lambda.
+ * @param consumer What to execute.
+ * @return The Duration of execution.
+ * @throws E thrown by the lambda.
+ * @since 3.13.0
+ */
+ public static <E extends Throwable> Duration of(final FailableConsumer<Instant, E> consumer) throws E {
+ return since(now(consumer::accept));
+ }
+
+ /**
+ * Runs the lambda and returns the duration of its execution.
+ *
+ * @param <E> The type of exception throw by the lambda.
+ * @param runnable What to execute.
+ * @return The Duration of execution.
+ * @throws E thrown by the lambda.
+ * @since 3.13.0
+ */
+ public static <E extends Throwable> Duration of(final FailableRunnable<E> runnable) throws E {
+ return of(start -> runnable.run());
+ }
+
+ private static <E extends Throwable> Instant now(final FailableConsumer<Instant, E> nowConsumer) throws E {
+ final Instant start = Instant.now();
+ nowConsumer.accept(start);
+ return start;
+ }
+
+ /**
+ * Computes the Duration between a start instant and now.
+ *
+ * @param startInclusive the start instant, inclusive, not null.
+ * @return a {@link Duration}, not null.
+ * @since 3.13.0
+ */
+ public static Duration since(final Temporal startInclusive) {
+ return Duration.between(startInclusive, Instant.now());
+ }
+
+ /**
+ * Converts a {@link TimeUnit} to a {@link ChronoUnit}.
+ *
+ * @param timeUnit A non-null TimeUnit.
+ * @return The corresponding ChronoUnit.
+ */
+ static ChronoUnit toChronoUnit(final TimeUnit timeUnit) {
+ // TODO when using Java >= 9: Use TimeUnit.toChronoUnit().
+ switch (Objects.requireNonNull(timeUnit)) {
+ case NANOSECONDS:
+ return ChronoUnit.NANOS;
+ case MICROSECONDS:
+ return ChronoUnit.MICROS;
+ case MILLISECONDS:
+ return ChronoUnit.MILLIS;
+ case SECONDS:
+ return ChronoUnit.SECONDS;
+ case MINUTES:
+ return ChronoUnit.MINUTES;
+ case HOURS:
+ return ChronoUnit.HOURS;
+ case DAYS:
+ return ChronoUnit.DAYS;
+ default:
+ throw new IllegalArgumentException(timeUnit.toString());
+ }
+ }
+
+ /**
+ * Converts an amount and TimeUnit into a Duration.
+ *
+ * @param amount the amount of the duration, measured in terms of the unit, positive or negative
+ * @param timeUnit the unit that the duration is measured in, must have an exact duration, not null
+ * @return a Duration.
+ */
+ public static Duration toDuration(final long amount, final TimeUnit timeUnit) {
+ return Duration.of(amount, toChronoUnit(timeUnit));
+ }
+
+ /**
+ * Converts a Duration to milliseconds bound to an int (instead of a long).
+ * <p>
+ * Handy for low-level APIs that take millisecond timeouts in ints rather than longs.
+ * </p>
+ * <ul>
+ * <li>If the duration milliseconds are greater than {@link Integer#MAX_VALUE}, then return
+ * {@link Integer#MAX_VALUE}.</li>
+ * <li>If the duration milliseconds are lesser than {@link Integer#MIN_VALUE}, then return
+ * {@link Integer#MIN_VALUE}.</li>
+ * </ul>
+ *
+ * @param duration The duration to convert, not null.
+ * @return int milliseconds.
+ */
+ public static int toMillisInt(final Duration duration) {
+ Objects.requireNonNull(duration, "duration");
+ // intValue() does not do a narrowing conversion here
+ return LONG_TO_INT_RANGE.fit(Long.valueOf(duration.toMillis())).intValue();
+ }
+
+ /**
+ * Returns the given non-null value or {@link Duration#ZERO} if null.
+ *
+ * @param duration The duration to test.
+ * @return The given duration or {@link Duration#ZERO}.
+ */
+ public static Duration zeroIfNull(final Duration duration) {
+ return ObjectUtils.defaultIfNull(duration, Duration.ZERO);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
new file mode 100644
index 000000000..7bec111bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
@@ -0,0 +1,671 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * FastDateFormat is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.
+ *
+ * <p>To obtain an instance of FastDateFormat, use one of the static factory methods:
+ * {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)},
+ * {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)}
+ * </p>
+ *
+ * <p>Since FastDateFormat is thread safe, you can use a static member instance:</p>
+ * <code>
+ * private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
+ * </code>
+ *
+ * <p>This class can be used as a direct replacement to
+ * {@link SimpleDateFormat} in most formatting and parsing situations.
+ * This class is especially useful in multi-threaded server environments.
+ * {@link SimpleDateFormat} is not thread-safe in any JDK version,
+ * nor will it be as Sun have closed the bug/RFE.
+ * </p>
+ *
+ * <p>All patterns are compatible with
+ * SimpleDateFormat (except time zones and some year patterns - see below).</p>
+ *
+ * <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p>
+ *
+ * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
+ * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
+ * This pattern letter can be used here (on all JDK versions).</p>
+ *
+ * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
+ * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
+ * This introduces a minor incompatibility with Java 1.4, but at a gain of
+ * useful functionality.</p>
+ *
+ * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
+ * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
+ * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
+ * 'YYY' will be formatted as '2003', while it was '03' in former Java
+ * versions. FastDateFormat implements the behavior of Java 7.</p>
+ *
+ * @since 2.0
+ */
+public class FastDateFormat extends Format implements DateParser, DatePrinter {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 2L;
+
+ /**
+ * FULL locale dependent date or time style.
+ */
+
+ public static final int FULL = DateFormat.FULL;
+
+ /**
+ * LONG locale dependent date or time style.
+ */
+ public static final int LONG = DateFormat.LONG;
+
+ /**
+ * MEDIUM locale dependent date or time style.
+ */
+ public static final int MEDIUM = DateFormat.MEDIUM;
+
+ /**
+ * SHORT locale dependent date or time style.
+ */
+ public static final int SHORT = DateFormat.SHORT;
+
+ private static final FormatCache<FastDateFormat> cache = new FormatCache<FastDateFormat>() {
+ @Override
+ protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(pattern, timeZone, locale);
+ }
+ };
+
+ /** 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.
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style) {
+ return cache.getDateInstance(style, null, null);
+ }
+
+ /**
+ * Gets a date formatter instance using the specified style and
+ * locale in the default time zone.
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style, final Locale locale) {
+ return cache.getDateInstance(style, null, locale);
+ }
+
+ /**
+ * Gets a date formatter instance using the specified style and
+ * time zone in the default locale.
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
+ return cache.getDateInstance(style, timeZone, null);
+ }
+
+ /**
+ * Gets a date formatter instance using the specified style, time
+ * zone and locale.
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ */
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ return cache.getDateInstance(style, timeZone, locale);
+ }
+
+ /**
+ * Gets a time formatter instance using the specified style in the
+ * default time zone and locale.
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style) {
+ return cache.getTimeInstance(style, null, null);
+ }
+
+ /**
+ * Gets a time formatter instance using the specified style and
+ * locale in the default time zone.
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
+ return cache.getTimeInstance(style, null, locale);
+ }
+
+ /**
+ * Gets a time formatter instance using the specified style and
+ * time zone in the default locale.
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
+ return cache.getTimeInstance(style, timeZone, null);
+ }
+
+ /**
+ * Gets a time formatter instance using the specified style, time
+ * zone and locale.
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ */
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ 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);
+ }
+
+ // Constructor
+ /**
+ * Constructs a new FastDateFormat.
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
+ this(pattern, timeZone, locale, null);
+ }
+
+ // Constructor
+ /**
+ * Constructs a new FastDateFormat.
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @param centuryStart The start of the 100-year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+ printer = new FastDatePrinter(pattern, timeZone, locale);
+ 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)}
+ *
+ * @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));
+ }
+
+ /**
+ * 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 {@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);
+ }
+
+ /**
+ * Formats a {@link Calendar} object.
+ *
+ * @param calendar the calendar to format
+ * @return the formatted string
+ */
+ @Override
+ public String format(final Calendar calendar) {
+ return printer.format(calendar);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 {@link Calendar} object into the
+ * supplied {@link StringBuffer}.
+ *
+ * @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
+ @Override
+ public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
+ return printer.format(calendar, 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
+ * @since 3.5
+ */
+ @Override
+ public <B extends Appendable> B format(final long millis, final B 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
+ * @since 3.5
+ */
+ @Override
+ public <B extends Appendable> B format(final Date date, final B buf) {
+ return printer.format(date, buf);
+ }
+
+ /**
+ * Formats a {@link Calendar} object into the
+ * supplied {@link StringBuffer}.
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ * @since 3.5
+ */
+ @Override
+ public <B extends Appendable> B format(final Calendar calendar, final B buf) {
+ return printer.format(calendar, buf);
+ }
+
+ // Parsing
+
+
+ /* (non-Javadoc)
+ * @see DateParser#parse(String)
+ */
+ @Override
+ public Date parse(final String source) throws ParseException {
+ return parser.parse(source);
+ }
+
+ /* (non-Javadoc)
+ * @see DateParser#parse(String, java.text.ParsePosition)
+ */
+ @Override
+ public Date parse(final String source, final ParsePosition pos) {
+ return parser.parse(source, pos);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition, java.util.Calendar)
+ */
+ @Override
+ public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
+ return parser.parse(source, pos, calendar);
+ }
+
+ /* (non-Javadoc)
+ * @see java.text.Format#parseObject(String, java.text.ParsePosition)
+ */
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ 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.
+ *
+ * <p>This zone is always used for {@link Date} formatting.</p>
+ *
+ * @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.
+ *
+ * <p>The actual formatted length will almost always be less than or
+ * equal to this amount.</p>
+ *
+ * @return the maximum formatted length
+ */
+ public int getMaxLengthEstimate() {
+ return 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.
+ *
+ * @return a debugging string
+ */
+ @Override
+ 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
new file mode 100644
index 000000000..979bf6028
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -0,0 +1,1074 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.LocaleUtils;
+
+/**
+ * FastDateParser is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.
+ *
+ * <p>To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
+ * or another variation of the factory methods of {@link FastDateFormat}.</p>
+ *
+ * <p>Since FastDateParser is thread safe, you can use a static member instance:</p>
+ * <code>
+ * private static final DateParser DATE_PARSER = FastDateFormat.getInstance("yyyy-MM-dd");
+ * </code>
+ *
+ * <p>This class can be used as a direct replacement for
+ * {@link SimpleDateFormat} in most parsing situations.
+ * This class is especially useful in multi-threaded server environments.
+ * {@link SimpleDateFormat} is not thread-safe in any JDK version,
+ * nor will it be as Sun has closed the
+ * <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
+ * </p>
+ *
+ * <p>Only parsing is supported by this class, but all patterns are compatible with
+ * SimpleDateFormat.</p>
+ *
+ * <p>The class operates in lenient mode, so for example a time of 90 minutes is treated as 1 hour 30 minutes.</p>
+ *
+ * <p>Timing tests indicate this class is as about as fast as SimpleDateFormat
+ * in single thread applications and about 25% faster in multi-thread applications.</p>
+ *
+ * @since 3.2
+ * @see FastDatePrinter
+ */
+public class FastDateParser implements DateParser, Serializable {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 3L;
+
+ static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP");
+
+ /** 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<StrategyAndWidth> 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<String> 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 non-null 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 = pattern;
+ this.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);
+ }
+
+ /**
+ * 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 fm = new StrategyParser(definingCalendar);
+ for (;;) {
+ final StrategyAndWidth field = fm.getNextStrategy();
+ if (field == null) {
+ break;
+ }
+ patterns.add(field);
+ }
+ }
+
+ // helper classes to parse the format string
+
+ /**
+ * Holds strategy and field width
+ */
+ private static class StrategyAndWidth {
+
+ final Strategy strategy;
+ final int width;
+
+ StrategyAndWidth(final Strategy strategy, final int width) {
+ this.strategy = strategy;
+ this.width = width;
+ }
+
+ int getMaxWidth(final ListIterator<StrategyAndWidth> lt) {
+ if (!strategy.isNumber() || !lt.hasNext()) {
+ return 0;
+ }
+ final Strategy nextStrategy = lt.next().strategy;
+ lt.previous();
+ return nextStrategy.isNumber() ? width : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "StrategyAndWidth [strategy=" + strategy + ", width=" + width + "]";
+ }
+ }
+
+ /**
+ * Parse format into Strategies
+ */
+ private class StrategyParser {
+ private final Calendar definingCalendar;
+ private int currentIdx;
+
+ StrategyParser(final Calendar definingCalendar) {
+ this.definingCalendar = definingCalendar;
+ }
+
+ StrategyAndWidth getNextStrategy() {
+ if (currentIdx >= pattern.length()) {
+ return null;
+ }
+
+ final char c = pattern.charAt(currentIdx);
+ if (isFormatLetter(c)) {
+ return letterPattern(c);
+ }
+ return literal();
+ }
+
+ private StrategyAndWidth letterPattern(final char c) {
+ final int begin = currentIdx;
+ while (++currentIdx < pattern.length()) {
+ if (pattern.charAt(currentIdx) != c) {
+ break;
+ }
+ }
+
+ final int width = currentIdx - begin;
+ return new StrategyAndWidth(getStrategy(c, width, definingCalendar), width);
+ }
+
+ private StrategyAndWidth literal() {
+ boolean activeQuote = false;
+
+ final StringBuilder sb = new StringBuilder();
+ while (currentIdx < pattern.length()) {
+ final char c = pattern.charAt(currentIdx);
+ if (!activeQuote && isFormatLetter(c)) {
+ break;
+ }
+ if (c == '\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\'')) {
+ activeQuote = !activeQuote;
+ continue;
+ }
+ ++currentIdx;
+ sb.append(c);
+ }
+
+ if (activeQuote) {
+ throw new IllegalArgumentException("Unterminated quote");
+ }
+
+ final String formatField = sb.toString();
+ return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
+ }
+ }
+
+ 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 re supported date range
+ if (locale.equals(JAPANESE_IMPERIAL)) {
+ throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\n"
+ + "Unparseable 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.
+ * <p>
+ * 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<StrategyAndWidth> 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<String, Integer> appendDisplayNames(final Calendar calendar, final Locale locale, final int field,
+ final StringBuilder regex) {
+ final Map<String, Integer> values = new HashMap<>();
+ final Locale actualLocale = LocaleUtils.toLocale(locale);
+ final Map<String, Integer> displayNames = calendar.getDisplayNames(field, Calendar.ALL_STYLES, actualLocale);
+ final TreeSet<String> 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<Locale, Strategy>[] 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<Locale, Strategy> 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<Locale, Strategy> 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 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 class CaseInsensitiveTextStrategy extends PatternStrategy {
+ private final int field;
+ final Locale locale;
+ private final Map<String, Integer> 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<String, TzInfo> tzNames = new HashMap<>();
+
+ private static class TzInfo {
+ final TimeZone zone;
+ final int dstOffset;
+
+ TzInfo(final TimeZone tz, final boolean useDst) {
+ zone = tz;
+ dstOffset = useDst ? tz.getDSTSavings() : 0;
+ }
+ }
+
+ /**
+ * Index of zone id
+ */
+ private static final int ID = 0;
+
+ /**
+ * Constructs a Strategy that parses a TimeZone
+ *
+ * @param locale The Locale
+ */
+ TimeZoneStrategy(final Locale locale) {
+ this.locale = LocaleUtils.toLocale(locale);
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION);
+
+ final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
+
+ final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
+ for (final String[] zoneNames : zones) {
+ // offset 0 is the time zone ID and is not localized
+ final String tzId = zoneNames[ID];
+ if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
+ continue;
+ }
+ final TimeZone tz = TimeZone.getTimeZone(tzId);
+ // offset 1 is long standard name
+ // offset 2 is short standard name
+ final TzInfo standard = new TzInfo(tz, false);
+ TzInfo tzInfo = standard;
+ for (int i = 1; i < zoneNames.length; ++i) {
+ switch (i) {
+ case 3: // offset 3 is long daylight savings (or summertime) name
+ // offset 4 is the short summertime name
+ tzInfo = new TzInfo(tz, true);
+ break;
+ case 5: // offset 5 starts additional names, probably standard time
+ tzInfo = standard;
+ break;
+ default:
+ break;
+ }
+ if (zoneNames[i] != null) {
+ final String key = zoneNames[i].toLowerCase(locale);
+ // ignore the data associated with duplicates supplied in
+ // the additional names
+ if (sorted.add(key)) {
+ tzNames.put(key, tzInfo);
+ }
+ }
+ }
+ }
+ // order the regex alternatives with longer strings first, greedy
+ // match will ensure the longest string will be consumed
+ sorted.forEach(zoneName -> simpleQuote(sb.append('|'), zoneName));
+ sb.append(")");
+ createPattern(sb);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(final FastDateParser parser, final Calendar calendar, final String timeZone) {
+ final TimeZone tz = FastTimeZone.getGmtTimeZone(timeZone);
+ if (tz != null) {
+ calendar.setTimeZone(tz);
+ } else {
+ final String lowerCase = timeZone.toLowerCase(locale);
+ TzInfo tzInfo = tzNames.get(lowerCase);
+ if (tzInfo == null) {
+ // match missing the optional trailing period
+ tzInfo = tzNames.get(lowerCase + '.');
+ }
+ calendar.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
+ calendar.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
+ }
+ }
+
+ /**
+ * Converts this instance to a handy debug string.
+ *
+ * @since 3.12.0
+ */
+ @Override
+ public String toString() {
+ return "TimeZoneStrategy [locale=" + locale + ", tzNames=" + tzNames + ", pattern=" + pattern + "]";
+ }
+
+ }
+
+ private static class ISO8601TimeZoneStrategy extends PatternStrategy {
+ // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
+
+ /**
+ * 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));
+ }
+
+ 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
+ int modify(final FastDateParser parser, final int iValue) {
+ return iValue-1;
+ }
+ };
+
+ 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) {
+ return iValue == 7 ? Calendar.SUNDAY : iValue + 1;
+ }
+ };
+
+ 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) {
+ return iValue == 24 ? 0 : iValue;
+ }
+ };
+
+ private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
+ @Override
+ int modify(final FastDateParser parser, final int iValue) {
+ return iValue == 12 ? 0 : iValue;
+ }
+ };
+
+ 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);
+ private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
new file mode 100644
index 000000000..d86b25e32
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
@@ -0,0 +1,1573 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+/**
+ * FastDatePrinter is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.
+ *
+ * <p>To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
+ * or another variation of the factory methods of {@link FastDateFormat}.</p>
+ *
+ * <p>Since FastDatePrinter is thread safe, you can use a static member instance:</p>
+ * <code>
+ * private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd");
+ * </code>
+ *
+ * <p>This class can be used as a direct replacement to
+ * {@link SimpleDateFormat} in most formatting situations.
+ * This class is especially useful in multi-threaded server environments.
+ * {@link SimpleDateFormat} is not thread-safe in any JDK version,
+ * nor will it be as Sun have closed the bug/RFE.
+ * </p>
+ *
+ * <p>Only formatting is supported by this class, but all patterns are compatible with
+ * SimpleDateFormat (except time zones and some year patterns - see below).</p>
+ *
+ * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
+ * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
+ * This pattern letter can be used here (on all JDK versions).</p>
+ *
+ * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
+ * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
+ * This introduces a minor incompatibility with Java 1.4, but at a gain of
+ * useful functionality.</p>
+ *
+ * <p>Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}.
+ * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using
+ * one of the {@code 'X'} formats is recommended.
+ *
+ * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
+ * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
+ * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
+ * 'YYY' will be formatted as '2003', while it was '03' in former Java
+ * versions. FastDatePrinter implements the behavior of Java 7.</p>
+ *
+ * @since 3.2
+ * @see FastDateParser
+ */
+public class FastDatePrinter implements DatePrinter, Serializable {
+ // A lot of the speed in this class comes from caching, but some comes
+ // from the special int to StringBuffer conversion.
+ //
+ // The following produces a padded 2-digit number:
+ // buffer.append((char)(value / 10 + '0'));
+ // buffer.append((char)(value % 10 + '0'));
+ //
+ // Note that the fastest append to StringBuffer is a single char (used here).
+ // Note that Integer.toString() is not called, the conversion is simply
+ // taking the value and adding (mathematically) the ASCII value for '0'.
+ // So, don't change this code! It works and is very fast.
+
+ /** Empty array. */
+ private static final Rule[] EMPTY_RULE_ARRAY = {};
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * FULL locale dependent date or time style.
+ */
+ public static final int FULL = DateFormat.FULL;
+ /**
+ * LONG locale dependent date or time style.
+ */
+ public static final int LONG = DateFormat.LONG;
+ /**
+ * MEDIUM locale dependent date or time style.
+ */
+ public static final int MEDIUM = DateFormat.MEDIUM;
+ /**
+ * SHORT locale dependent date or time style.
+ */
+ public static final int SHORT = DateFormat.SHORT;
+
+ /**
+ * 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.
+ */
+ private transient int maxLengthEstimate;
+
+ // Constructor
+ /**
+ * Constructs a new FastDatePrinter.
+ * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the
+ * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance.
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
+ this.pattern = pattern;
+ this.timeZone = timeZone;
+ this.locale = LocaleUtils.toLocale(locale);
+ init();
+ }
+
+ /**
+ * Initializes the instance for first use.
+ */
+ private void init() {
+ final List<Rule> rulesList = parsePattern();
+ rules = rulesList.toArray(EMPTY_RULE_ARRAY);
+
+ int len = 0;
+ for (int i = rules.length; --i >= 0;) {
+ len += rules[i].estimateLength();
+ }
+
+ maxLengthEstimate = len;
+ }
+
+ // Parse the pattern
+ /**
+ * Returns a list of Rules given a pattern.
+ *
+ * @return a {@link List} of Rule objects
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ protected List<Rule> parsePattern() {
+ final DateFormatSymbols symbols = new DateFormatSymbols(locale);
+ final List<Rule> rules = new ArrayList<>();
+
+ final String[] ERAs = symbols.getEras();
+ final String[] months = symbols.getMonths();
+ final String[] shortMonths = symbols.getShortMonths();
+ final String[] weekdays = symbols.getWeekdays();
+ final String[] shortWeekdays = symbols.getShortWeekdays();
+ final String[] AmPmStrings = symbols.getAmPmStrings();
+
+ final int length = pattern.length();
+ final int[] indexRef = new int[1];
+
+ for (int i = 0; i < length; i++) {
+ indexRef[0] = i;
+ final String token = parseToken(pattern, indexRef);
+ i = indexRef[0];
+
+ final int tokenLen = token.length();
+ if (tokenLen == 0) {
+ break;
+ }
+
+ Rule rule;
+ final char c = token.charAt(0);
+
+ switch (c) {
+ case 'G': // era designator (text)
+ rule = new TextField(Calendar.ERA, ERAs);
+ break;
+ case 'y': // year (number)
+ case 'Y': // week year
+ if (tokenLen == 2) {
+ rule = TwoDigitYearField.INSTANCE;
+ } else {
+ rule = selectNumberRule(Calendar.YEAR, Math.max(tokenLen, 4));
+ }
+ if (c == 'Y') {
+ rule = new WeekYear((NumberRule) rule);
+ }
+ break;
+ case 'M': // month in year (text and number)
+ if (tokenLen >= 4) {
+ rule = new TextField(Calendar.MONTH, months);
+ } else if (tokenLen == 3) {
+ rule = new TextField(Calendar.MONTH, shortMonths);
+ } else if (tokenLen == 2) {
+ rule = TwoDigitMonthField.INSTANCE;
+ } else {
+ rule = UnpaddedMonthField.INSTANCE;
+ }
+ break;
+ case 'L': // month in year (text and number)
+ if (tokenLen >= 4) {
+ rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneLongMonthNames());
+ } else if (tokenLen == 3) {
+ rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneShortMonthNames());
+ } else if (tokenLen == 2) {
+ rule = TwoDigitMonthField.INSTANCE;
+ } else {
+ rule = UnpaddedMonthField.INSTANCE;
+ }
+ break;
+ case 'd': // day in month (number)
+ rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
+ break;
+ case 'h': // hour in am/pm (number, 1..12)
+ rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
+ break;
+ case 'H': // hour in day (number, 0..23)
+ rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
+ break;
+ case 'm': // minute in hour (number)
+ rule = selectNumberRule(Calendar.MINUTE, tokenLen);
+ break;
+ case 's': // second in minute (number)
+ rule = selectNumberRule(Calendar.SECOND, tokenLen);
+ break;
+ case 'S': // millisecond (number)
+ rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
+ break;
+ case 'E': // day in week (text)
+ rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
+ break;
+ case 'u': // day in week (number)
+ rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen));
+ break;
+ case 'D': // day in year (number)
+ rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
+ break;
+ case 'F': // day of week in month (number)
+ rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
+ break;
+ case 'w': // week in year (number)
+ rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
+ break;
+ case 'W': // week in month (number)
+ rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
+ break;
+ case 'a': // am/pm marker (text)
+ rule = new TextField(Calendar.AM_PM, AmPmStrings);
+ break;
+ case 'k': // hour in day (1..24)
+ rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
+ break;
+ case 'K': // hour in am/pm (0..11)
+ rule = selectNumberRule(Calendar.HOUR, tokenLen);
+ break;
+ case 'X': // ISO 8601
+ rule = Iso8601_Rule.getRule(tokenLen);
+ break;
+ case 'z': // time zone (text)
+ if (tokenLen >= 4) {
+ rule = new TimeZoneNameRule(timeZone, locale, TimeZone.LONG);
+ } else {
+ rule = new TimeZoneNameRule(timeZone, locale, TimeZone.SHORT);
+ }
+ break;
+ case 'Z': // time zone (value)
+ if (tokenLen == 1) {
+ rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
+ } else if (tokenLen == 2) {
+ rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
+ } else {
+ rule = TimeZoneNumberRule.INSTANCE_COLON;
+ }
+ break;
+ case '\'': // literal text
+ final String sub = token.substring(1);
+ if (sub.length() == 1) {
+ rule = new CharacterLiteral(sub.charAt(0));
+ } else {
+ rule = new StringLiteral(sub);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal pattern component: " + token);
+ }
+
+ rules.add(rule);
+ }
+
+ return rules;
+ }
+
+ /**
+ * Performs the parsing of tokens.
+ *
+ * @param pattern the pattern
+ * @param indexRef index references
+ * @return parsed token
+ */
+ protected String parseToken(final String pattern, final int[] indexRef) {
+ final StringBuilder buf = new StringBuilder();
+
+ int i = indexRef[0];
+ final int length = pattern.length();
+
+ char c = pattern.charAt(i);
+ if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
+ // Scan a run of the same character, which indicates a time
+ // pattern.
+ buf.append(c);
+
+ while (i + 1 < length) {
+ final char peek = pattern.charAt(i + 1);
+ if (peek != c) {
+ break;
+ }
+ buf.append(c);
+ i++;
+ }
+ } else {
+ // This will identify token as text.
+ buf.append('\'');
+
+ boolean inLiteral = false;
+
+ for (; i < length; i++) {
+ c = pattern.charAt(i);
+
+ if (c == '\'') {
+ if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
+ // '' is treated as escaped '
+ i++;
+ buf.append(c);
+ } else {
+ inLiteral = !inLiteral;
+ }
+ } else if (!inLiteral &&
+ (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
+ i--;
+ break;
+ } else {
+ buf.append(c);
+ }
+ }
+ }
+
+ indexRef[0] = i;
+ return buf.toString();
+ }
+
+ /**
+ * Gets an appropriate rule for the padding required.
+ *
+ * @param field the field to get a rule for
+ * @param padding the padding required
+ * @return a new rule with the correct padding
+ */
+ protected NumberRule selectNumberRule(final int field, final int padding) {
+ switch (padding) {
+ case 1:
+ return new UnpaddedNumberField(field);
+ case 2:
+ return new TwoDigitNumberField(field);
+ default:
+ return new PaddedNumberField(field, padding);
+ }
+ }
+
+ // Format methods
+ /**
+ * Formats a {@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, "<null>"));
+ }
+
+ /**
+ * 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, "<null>"));
+ }
+
+ /* (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 extends Appendable> 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 extends Appendable> 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 extends Appendable> 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 <B> the Appendable class type, usually StringBuilder or StringBuffer.
+ * @return the specified string buffer
+ */
+ private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
+ try {
+ for (final Rule rule : rules) {
+ rule.appendTo(buf, calendar);
+ }
+ } catch (final IOException ioe) {
+ ExceptionUtils.rethrow(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.
+ *
+ * <p>The actual formatted length will almost always be less than or
+ * equal to this amount.</p>
+ *
+ * @return the maximum formatted length
+ */
+ public int getMaxLengthEstimate() {
+ return 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.
+ *
+ * @return a debugging string
+ */
+ @Override
+ 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 mValue;
+
+ /**
+ * Constructs a new instance of {@link CharacterLiteral}
+ * to hold the specified value.
+ *
+ * @param value the character literal
+ */
+ CharacterLiteral(final char value) {
+ mValue = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return 1;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ buffer.append(mValue);
+ }
+ }
+
+ /**
+ * Inner class to output a constant string.
+ */
+ private static class StringLiteral implements Rule {
+ private final String mValue;
+
+ /**
+ * Constructs a new instance of {@link StringLiteral}
+ * to hold the specified value.
+ *
+ * @param value the string literal
+ */
+ StringLiteral(final String value) {
+ mValue = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return mValue.length();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ buffer.append(mValue);
+ }
+ }
+
+ /**
+ * Inner class to output one of a set of values.
+ */
+ private static class TextField implements Rule {
+ private final int mField;
+ private final String[] mValues;
+
+ /**
+ * Constructs an instance of {@link TextField}
+ * with the specified field and values.
+ *
+ * @param field the field
+ * @param values the field values
+ */
+ TextField(final int field, final String[] values) {
+ mField = field;
+ mValues = values;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ int max = 0;
+ for (int i=mValues.length; --i >= 0; ) {
+ final int len = mValues[i].length();
+ if (len > max) {
+ max = len;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ buffer.append(mValues[calendar.get(mField)]);
+ }
+ }
+
+ /**
+ * Inner class to output an unpadded number.
+ */
+ private static class UnpaddedNumberField implements NumberRule {
+ private final int mField;
+
+ /**
+ * Constructs an instance of {@link UnpaddedNumberField} with the specified field.
+ *
+ * @param field the field
+ */
+ UnpaddedNumberField(final int field) {
+ mField = 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(mField));
+ }
+
+ /**
+ * {@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 mField;
+ private final int mSize;
+
+ /**
+ * 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();
+ }
+ mField = field;
+ mSize = size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return mSize;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ appendTo(buffer, calendar.get(mField));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void appendTo(final Appendable buffer, final int value) throws IOException {
+ appendFullDigits(buffer, value, mSize);
+ }
+ }
+
+ /**
+ * Inner class to output a two digit number.
+ */
+ private static class TwoDigitNumberField implements NumberRule {
+ private final int mField;
+
+ /**
+ * Constructs an instance of {@link TwoDigitNumberField} with the specified field.
+ *
+ * @param field the field
+ */
+ TwoDigitNumberField(final int field) {
+ mField = 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(mField));
+ }
+
+ /**
+ * {@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 mRule;
+
+ /**
+ * Constructs an instance of {@link TwelveHourField} with the specified
+ * {@link NumberRule}.
+ *
+ * @param rule the rule
+ */
+ TwelveHourField(final NumberRule rule) {
+ mRule = rule;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return mRule.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;
+ }
+ mRule.appendTo(buffer, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final int value) throws IOException {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+ /**
+ * Inner class to output the twenty four hour field.
+ */
+ private static class TwentyFourHourField implements NumberRule {
+ private final NumberRule mRule;
+
+ /**
+ * Constructs an instance of {@link TwentyFourHourField} with the specified
+ * {@link NumberRule}.
+ *
+ * @param rule the rule
+ */
+ TwentyFourHourField(final NumberRule rule) {
+ mRule = rule;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return mRule.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;
+ }
+ mRule.appendTo(buffer, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final Appendable buffer, final int value) throws IOException {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+ /**
+ * Inner class to output the numeric day in week.
+ */
+ private static class DayInWeekField implements NumberRule {
+ private final NumberRule mRule;
+
+ DayInWeekField(final NumberRule rule) {
+ mRule = rule;
+ }
+
+ @Override
+ public int estimateLength() {
+ return mRule.estimateLength();
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ final int value = calendar.get(Calendar.DAY_OF_WEEK);
+ mRule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1);
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final int value) throws IOException {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+ /**
+ * Inner class to output the numeric day in week.
+ */
+ private static class WeekYear implements NumberRule {
+ private final NumberRule mRule;
+
+ WeekYear(final NumberRule rule) {
+ mRule = rule;
+ }
+
+ @Override
+ public int estimateLength() {
+ return mRule.estimateLength();
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
+ mRule.appendTo(buffer, calendar.getWeekYear());
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final int value) throws IOException {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
+
+ private static final ConcurrentMap<TimeZoneDisplayKey, String> 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 mLocale;
+ private final int mStyle;
+ private final String mStandard;
+ private final String mDaylight;
+
+ /**
+ * 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) {
+ mLocale = LocaleUtils.toLocale(locale);
+ mStyle = style;
+
+ mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
+ mDaylight = 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(mStandard.length(), mDaylight.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, mStyle, mLocale));
+ } else {
+ buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale));
+ }
+ }
+ }
+
+ /**
+ * Inner class to output a time zone as a number {@code +/-HHMM}
+ * or {@code +/-HH:MM}.
+ */
+ private static class TimeZoneNumberRule implements Rule {
+ static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
+ static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
+
+ final boolean mColon;
+
+ /**
+ * Constructs an instance of {@link TimeZoneNumberRule} with the specified properties.
+ *
+ * @param colon add colon between HH and MM in the output if {@code true}
+ */
+ TimeZoneNumberRule(final boolean colon) {
+ mColon = 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 (mColon) {
+ 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");
+ }
+ }
+
+ 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 mTimeZone;
+ private final int mStyle;
+ private final Locale mLocale;
+
+ /**
+ * 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) {
+ mTimeZone = timeZone;
+ if (daylight) {
+ mStyle = style | 0x80000000;
+ } else {
+ mStyle = style;
+ }
+ mLocale = LocaleUtils.toLocale(locale);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof TimeZoneDisplayKey) {
+ final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
+ return
+ mTimeZone.equals(other.mTimeZone) &&
+ mStyle == other.mStyle &&
+ mLocale.equals(other.mLocale);
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java b/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java
new file mode 100644
index 000000000..d111bed41
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Faster methods to produce custom time zones.
+ *
+ * @since 3.7
+ */
+public class FastTimeZone {
+
+ private static final Pattern GMT_PATTERN = Pattern.compile("^(?:(?i)GMT)?([+-])?(\\d\\d?)?(:?(\\d\\d?))?$");
+
+ private static final TimeZone GREENWICH = new GmtTimeZone(false, 0, 0);
+
+ /**
+ * Gets the GMT TimeZone.
+ * @return A TimeZone with a raw offset of zero.
+ */
+ public static TimeZone getGmtTimeZone() {
+ return GREENWICH;
+ }
+
+ /**
+ * Gets a TimeZone with GMT offsets. A GMT offset must be either 'Z', or 'UTC', or match
+ * <em>(GMT)? hh?(:?mm?)?</em>, where h and m are digits representing hours and minutes.
+ *
+ * @param pattern The GMT offset
+ * @return A TimeZone with offset from GMT or null, if pattern does not match.
+ */
+ public static TimeZone getGmtTimeZone(final String pattern) {
+ if ("Z".equals(pattern) || "UTC".equals(pattern)) {
+ return GREENWICH;
+ }
+
+ final Matcher m = GMT_PATTERN.matcher(pattern);
+ if (m.matches()) {
+ final int hours = parseInt(m.group(2));
+ final int minutes = parseInt(m.group(4));
+ if (hours == 0 && minutes == 0) {
+ return GREENWICH;
+ }
+ return new GmtTimeZone(parseSign(m.group(1)), hours, minutes);
+ }
+ return null;
+ }
+
+ /**
+ * Gets a TimeZone, looking first for GMT custom ids, then falling back to Olson ids.
+ * A GMT custom id can be 'Z', or 'UTC', or has an optional prefix of GMT,
+ * followed by sign, hours digit(s), optional colon(':'), and optional minutes digits.
+ * i.e. <em>[GMT] (+|-) Hours [[:] Minutes]</em>
+ *
+ * @param id A GMT custom id (or Olson id
+ * @return A time zone
+ */
+ public static TimeZone getTimeZone(final String id) {
+ final TimeZone tz = getGmtTimeZone(id);
+ if (tz != null) {
+ return tz;
+ }
+ return TimeZone.getTimeZone(id);
+ }
+
+ private static int parseInt(final String group) {
+ return group != null ? Integer.parseInt(group) : 0;
+ }
+
+ private static boolean parseSign(final String group) {
+ return group != null && group.charAt(0) == '-';
+ }
+
+ // do not instantiate
+ private FastTimeZone() {
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/FormatCache.java b/src/main/java/org/apache/commons/lang3/time/FormatCache.java
new file mode 100644
index 000000000..38dbe1677
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/FormatCache.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.DateFormat;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.LocaleUtils;
+
+/**
+ * FormatCache is a cache and factory for {@link Format}s.
+ *
+ * @param <F> The Format type.
+ *
+ * @since 3.0
+ */
+// TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach.
+abstract class FormatCache<F extends Format> {
+
+ /**
+ * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
+ */
+ static final int NONE = -1;
+
+ private final ConcurrentMap<ArrayKey, F> cInstanceCache = new ConcurrentHashMap<>(7);
+
+ private static final ConcurrentMap<ArrayKey, String> cDateTimeInstanceCache = new ConcurrentHashMap<>(7);
+
+ /**
+ * Gets a formatter instance using the default pattern in the
+ * default time zone and locale.
+ *
+ * @return a date/time formatter
+ */
+ public F getInstance() {
+ return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
+ }
+
+ /**
+ * Gets a formatter instance using the specified pattern, time zone
+ * and locale.
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern, non-null
+ * @param timeZone the time zone, null means use the default TimeZone
+ * @param locale the locale, null means use the default Locale
+ * @return a pattern based date/time formatter
+ * @throws NullPointerException if pattern is {@code null}
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+ Objects.requireNonNull(pattern, "pattern");
+ final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone);
+ final Locale actualLocale = LocaleUtils.toLocale(locale);
+ final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale);
+ 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.
+ *
+ * @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 getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
+ 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/GmtTimeZone.java b/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java
new file mode 100644
index 000000000..82e2dc1b0
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.Date;
+import java.util.Objects;
+import java.util.TimeZone;
+
+/**
+ * Custom time zone that contains offset from GMT.
+ *
+ * @since 3.7
+ */
+class GmtTimeZone extends TimeZone {
+
+ private static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
+ private static final int MINUTES_PER_HOUR = 60;
+ private static final int HOURS_PER_DAY = 24;
+
+ // Serializable!
+ static final long serialVersionUID = 1L;
+
+ private static StringBuilder twoDigits(final StringBuilder sb, final int n) {
+ return sb.append((char) ('0' + (n / 10))).append((char) ('0' + (n % 10)));
+ }
+ private final int offset;
+
+ private final String zoneId;
+
+ GmtTimeZone(final boolean negate, final int hours, final int minutes) {
+ if (hours >= HOURS_PER_DAY) {
+ throw new IllegalArgumentException(hours + " hours out of range");
+ }
+ if (minutes >= MINUTES_PER_HOUR) {
+ throw new IllegalArgumentException(minutes + " minutes out of range");
+ }
+ final int milliseconds = (minutes + (hours * MINUTES_PER_HOUR)) * MILLISECONDS_PER_MINUTE;
+ offset = negate ? -milliseconds : milliseconds;
+ // @formatter:off
+ zoneId = twoDigits(twoDigits(new StringBuilder(9)
+ .append(TimeZones.GMT_ID)
+ .append(negate ? '-' : '+'), hours)
+ .append(':'), minutes)
+ .toString();
+ // @formatter:on
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof GmtTimeZone)) {
+ return false;
+ }
+ final GmtTimeZone other = (GmtTimeZone) obj;
+ return offset == other.offset && Objects.equals(zoneId, other.zoneId);
+ }
+
+ @Override
+ public String getID() {
+ return zoneId;
+ }
+
+ @Override
+ public int getOffset(final int era, final int year, final int month, final int day, final int dayOfWeek, final int milliseconds) {
+ return offset;
+ }
+
+ @Override
+ public int getRawOffset() {
+ return offset;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(offset, zoneId);
+ }
+
+ @Override
+ public boolean inDaylightTime(final Date date) {
+ return false;
+ }
+
+ @Override
+ public void setRawOffset(final int offsetMillis) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "[GmtTimeZone id=\"" + zoneId + "\",offset=" + offset + ']';
+ }
+
+ @Override
+ public boolean useDaylightTime() {
+ return false;
+ }
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/StopWatch.java b/src/main/java/org/apache/commons/lang3/time/StopWatch.java
new file mode 100644
index 000000000..e21ddd92e
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/StopWatch.java
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.time;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * {@link StopWatch} provides a convenient API for timings.
+ *
+ * <p>
+ * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
+ * </p>
+ * <ul>
+ * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
+ * remove the effect of the split. At this point, these three options are available again.</li>
+ * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
+ * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
+ * <li>{@link #stop()} the watch to complete the timing session.</li>
+ * </ul>
+ *
+ * <p>
+ * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
+ * split or suspend, however a suitable result will be returned at other points.
+ * </p>
+ *
+ * <p>
+ * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
+ * resume before suspend or unsplit before split.
+ * </p>
+ *
+ * <p>
+ * 1. split(), suspend(), or stop() cannot be invoked twice<br>
+ * 2. unsplit() may only be called if the watch has been split()<br>
+ * 3. resume() may only be called if the watch has been suspend()<br>
+ * 4. start() cannot be called twice without calling reset()
+ * </p>
+ *
+ * <p>This class is not thread-safe</p>
+ *
+ * @since 2.0
+ */
+public class StopWatch {
+
+ /**
+ * Enumeration type which indicates the split status of stopwatch.
+ */
+ private enum SplitState {
+ SPLIT,
+ UNSPLIT
+ }
+
+ /**
+ * Enumeration type which indicates the status of stopwatch.
+ */
+ private enum State {
+
+ RUNNING {
+ @Override
+ boolean isStarted() {
+ return true;
+ }
+ @Override
+ boolean isStopped() {
+ return false;
+ }
+ @Override
+ boolean isSuspended() {
+ return false;
+ }
+ },
+ STOPPED {
+ @Override
+ boolean isStarted() {
+ return false;
+ }
+ @Override
+ boolean isStopped() {
+ return true;
+ }
+ @Override
+ boolean isSuspended() {
+ return false;
+ }
+ },
+ SUSPENDED {
+ @Override
+ boolean isStarted() {
+ return true;
+ }
+ @Override
+ boolean isStopped() {
+ return false;
+ }
+ @Override
+ boolean isSuspended() {
+ return true;
+ }
+ },
+ UNSTARTED {
+ @Override
+ boolean isStarted() {
+ return false;
+ }
+ @Override
+ boolean isStopped() {
+ return true;
+ }
+ @Override
+ boolean isSuspended() {
+ return false;
+ }
+ };
+
+ /**
+ * Returns whether the StopWatch is started. A suspended StopWatch is also started watch.
+ *
+ * @return boolean If the StopWatch is started.
+ */
+ abstract boolean isStarted();
+
+ /**
+ * Returns whether the StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is
+ * considered as stopped.
+ *
+ * @return boolean If the StopWatch is stopped.
+ */
+ abstract boolean isStopped();
+
+ /**
+ * Returns whether the StopWatch is suspended.
+ *
+ * @return boolean
+ * If the StopWatch is suspended.
+ */
+ abstract boolean isSuspended();
+ }
+
+ private static final long NANO_2_MILLIS = 1000000L;
+
+ /**
+ * Creates a stopwatch for convenience.
+ *
+ * @return StopWatch a stopwatch.
+ *
+ * @since 3.10
+ */
+ public static StopWatch create() {
+ return new StopWatch();
+ }
+
+ /**
+ * Creates a started stopwatch for convenience.
+ *
+ * @return StopWatch a stopwatch that's already been started.
+ *
+ * @since 3.5
+ */
+ public static StopWatch createStarted() {
+ final StopWatch sw = new StopWatch();
+ sw.start();
+ return sw;
+ }
+
+ /**
+ * A message for string presentation.
+ *
+ * @since 3.10
+ */
+ private final String message;
+
+ /**
+ * The current running state of the StopWatch.
+ */
+ private State runningState = State.UNSTARTED;
+
+ /**
+ * Whether the stopwatch has a split time recorded.
+ */
+ private SplitState splitState = SplitState.UNSPLIT;
+
+ /**
+ * The start time in nanoseconds.
+ */
+ private long startTimeNanos;
+
+ /**
+ * The start time in milliseconds - nanoTime is only for elapsed time so we
+ * need to also store the currentTimeMillis to maintain the old
+ * getStartTime API.
+ */
+ private long startTimeMillis;
+
+ /**
+ * The end time in milliseconds - nanoTime is only for elapsed time so we
+ * need to also store the currentTimeMillis to maintain the old
+ * getStartTime API.
+ */
+ private long stopTimeMillis;
+
+ /**
+ * The stop time in nanoseconds.
+ */
+ private long stopTimeNanos;
+
+ /**
+ * Constructor.
+ *
+ */
+ public StopWatch() {
+ this(null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message A message for string presentation.
+ * @since 3.10
+ */
+ public StopWatch(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * Returns the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
+ *
+ * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
+ * @since 3.10
+ */
+ public String formatSplitTime() {
+ return DurationFormatUtils.formatDurationHMS(getSplitTime());
+ }
+
+ /**
+ * Returns the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
+ *
+ * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
+ * @since 3.10
+ */
+ public String formatTime() {
+ return DurationFormatUtils.formatDurationHMS(getTime());
+ }
+
+ /**
+ * Gets the message for string presentation.
+ *
+ * @return the message for string presentation.
+ * @since 3.10
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Gets the <em>elapsed</em> time in nanoseconds.
+ *
+ * <p>
+ * This is either the time between the start and the moment this method is called, or the amount of time between
+ * start and stop.
+ * </p>
+ *
+ * @return the <em>elapsed</em> time in nanoseconds.
+ * @see System#nanoTime()
+ * @since 3.0
+ */
+ public long getNanoTime() {
+ if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
+ return this.stopTimeNanos - this.startTimeNanos;
+ }
+ if (this.runningState == State.UNSTARTED) {
+ return 0;
+ }
+ if (this.runningState == State.RUNNING) {
+ return System.nanoTime() - this.startTimeNanos;
+ }
+ throw new IllegalStateException("Illegal running state has occurred.");
+ }
+
+ /**
+ * Gets the split time in nanoseconds.
+ *
+ * <p>
+ * This is the time between start and latest split.
+ * </p>
+ *
+ * @return the split time in nanoseconds
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not yet been split.
+ * @since 3.0
+ */
+ public long getSplitNanoTime() {
+ if (this.splitState != SplitState.SPLIT) {
+ throw new IllegalStateException("Stopwatch must be split to get the split time.");
+ }
+ return this.stopTimeNanos - this.startTimeNanos;
+ }
+
+ /**
+ * Gets the split time on the stopwatch.
+ *
+ * <p>
+ * This is the time between start and latest split.
+ * </p>
+ *
+ * @return the split time in milliseconds
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not yet been split.
+ * @since 2.1
+ */
+ public long getSplitTime() {
+ return getSplitNanoTime() / NANO_2_MILLIS;
+ }
+
+ /**
+ * Gets the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 1970
+ * UTC.
+ *
+ * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
+ * 1970 UTC.
+ * @throws IllegalStateException if this StopWatch has not been started
+ * @since 2.4
+ */
+ public long getStartTime() {
+ if (this.runningState == State.UNSTARTED) {
+ throw new IllegalStateException("Stopwatch has not been started");
+ }
+ // System.nanoTime is for elapsed time
+ return this.startTimeMillis;
+ }
+
+ /**
+ * Gets the time this stopwatch was stopped in milliseconds, between the current time and midnight, January 1, 1970
+ * UTC.
+ *
+ * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
+ * 1970 UTC.
+ * @throws IllegalStateException if this StopWatch has not been started
+ * @since 3.12.0
+ */
+ public long getStopTime() {
+ if (this.runningState == State.UNSTARTED) {
+ throw new IllegalStateException("Stopwatch has not been started");
+ }
+ // System.nanoTime is for elapsed time
+ return this.stopTimeMillis;
+ }
+
+ /**
+ * Gets the time on the stopwatch.
+ *
+ * <p>
+ * This is either the time between the start and the moment this method is called, or the amount of time between
+ * start and stop.
+ * </p>
+ *
+ * @return the time in milliseconds
+ */
+ public long getTime() {
+ return getNanoTime() / NANO_2_MILLIS;
+ }
+
+ /**
+ * Gets the time in the specified TimeUnit.
+ *
+ * <p>
+ * This is either the time between the start and the moment this method is called, or the amount of time between
+ * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down.
+ * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the
+ * result returned will be {@code 0}.
+ * </p>
+ *
+ * @param timeUnit the unit of time, not null
+ * @return the time in the specified TimeUnit, rounded down
+ * @since 3.5
+ */
+ public long getTime(final TimeUnit timeUnit) {
+ return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Returns whether the StopWatch is started. A suspended StopWatch is also started watch.
+ *
+ * @return boolean If the StopWatch is started.
+ * @since 3.2
+ */
+ public boolean isStarted() {
+ return runningState.isStarted();
+ }
+
+ /**
+ * Returns whether StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is considered
+ * as stopped.
+ *
+ * @return boolean If the StopWatch is stopped.
+ * @since 3.2
+ */
+ public boolean isStopped() {
+ return runningState.isStopped();
+ }
+
+ /**
+ * Returns whether the StopWatch is suspended.
+ *
+ * @return boolean
+ * If the StopWatch is suspended.
+ * @since 3.2
+ */
+ public boolean isSuspended() {
+ return runningState.isSuspended();
+ }
+
+ /**
+ * Resets the stopwatch. Stops it if need be.
+ *
+ * <p>
+ * This method clears the internal values to allow the object to be reused.
+ * </p>
+ */
+ public void reset() {
+ this.runningState = State.UNSTARTED;
+ this.splitState = SplitState.UNSPLIT;
+ }
+
+ /**
+ * Resumes the stopwatch after a suspend.
+ *
+ * <p>
+ * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
+ * resume calls in the total time.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not been suspended.
+ */
+ public void resume() {
+ if (this.runningState != State.SUSPENDED) {
+ throw new IllegalStateException("Stopwatch must be suspended to resume. ");
+ }
+ this.startTimeNanos += System.nanoTime() - this.stopTimeNanos;
+ this.runningState = State.RUNNING;
+ }
+
+ /**
+ * Splits the time.
+ *
+ * <p>
+ * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
+ * enabling {@link #unsplit()} to continue the timing from the original start point.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not running.
+ */
+ public void split() {
+ if (this.runningState != State.RUNNING) {
+ throw new IllegalStateException("Stopwatch is not running. ");
+ }
+ this.stopTimeNanos = System.nanoTime();
+ this.splitState = SplitState.SPLIT;
+ }
+
+ /**
+ * Starts the stopwatch.
+ *
+ * <p>
+ * This method starts a new timing session, clearing any previous values.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is already running.
+ */
+ public void start() {
+ if (this.runningState == State.STOPPED) {
+ throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
+ }
+ if (this.runningState != State.UNSTARTED) {
+ throw new IllegalStateException("Stopwatch already started. ");
+ }
+ this.startTimeNanos = System.nanoTime();
+ this.startTimeMillis = System.currentTimeMillis();
+ this.runningState = State.RUNNING;
+ }
+
+ /**
+ * Stops the stopwatch.
+ *
+ * <p>
+ * This method ends a new timing session, allowing the time to be retrieved.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not running.
+ */
+ public void stop() {
+ if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
+ throw new IllegalStateException("Stopwatch is not running. ");
+ }
+ if (this.runningState == State.RUNNING) {
+ this.stopTimeNanos = System.nanoTime();
+ this.stopTimeMillis = System.currentTimeMillis();
+ }
+ this.runningState = State.STOPPED;
+ }
+
+ /**
+ * Suspends the stopwatch for later resumption.
+ *
+ * <p>
+ * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
+ * resume calls in the total time.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch is not currently running.
+ */
+ public void suspend() {
+ if (this.runningState != State.RUNNING) {
+ throw new IllegalStateException("Stopwatch must be running to suspend. ");
+ }
+ this.stopTimeNanos = System.nanoTime();
+ this.stopTimeMillis = System.currentTimeMillis();
+ this.runningState = State.SUSPENDED;
+ }
+
+ /**
+ * Gets a summary of the split time that the stopwatch recorded as a string.
+ *
+ * <p>
+ * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
+ * </p>
+ *
+ * @return the split time as a String
+ * @since 2.1
+ * @since 3.10 Returns the prefix {@code "message "} if the message is set.
+ */
+ public String toSplitString() {
+ final String msgStr = Objects.toString(message, StringUtils.EMPTY);
+ final String formattedTime = formatSplitTime();
+ return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
+ }
+
+ /**
+ * Gets a summary of the time that the stopwatch recorded as a string.
+ *
+ * <p>
+ * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
+ * </p>
+ *
+ * @return the time as a String
+ * @since 3.10 Returns the prefix {@code "message "} if the message is set.
+ */
+ @Override
+ public String toString() {
+ final String msgStr = Objects.toString(message, StringUtils.EMPTY);
+ final String formattedTime = formatTime();
+ return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
+ }
+
+ /**
+ * Removes a split.
+ *
+ * <p>
+ * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
+ * continue.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if the StopWatch has not been split.
+ */
+ public void unsplit() {
+ if (this.splitState != SplitState.SPLIT) {
+ throw new IllegalStateException("Stopwatch has not been split. ");
+ }
+ this.splitState = SplitState.UNSPLIT;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/TimeZones.java b/src/main/java/org/apache/commons/lang3/time/TimeZones.java
new file mode 100644
index 000000000..c9fa4b24f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/TimeZones.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.time;
+
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * Helps to deal with {@link java.util.TimeZone}s.
+ *
+ * @since 3.7
+ */
+public class TimeZones {
+
+ /** Do not instantiate. */
+ private TimeZones() {
+ }
+
+ /**
+ * A public version of {@link java.util.TimeZone}'s package private {@code GMT_ID} field.
+ */
+ public static final String GMT_ID = "GMT";
+
+ /**
+ * The GMT time zone.
+ *
+ * @since 3.13.0
+ */
+ public static final TimeZone GMT = TimeZone.getTimeZone(GMT_ID);
+
+ /**
+ * Returns the given TimeZone if non-{@code null}, otherwise {@link TimeZone#getDefault()}.
+ *
+ * @param timeZone a locale or {@code null}.
+ * @return the given locale if non-{@code null}, otherwise {@link TimeZone#getDefault()}.
+ * @since 3.13.0
+ */
+ public static TimeZone toTimeZone(final TimeZone timeZone) {
+ return ObjectUtils.getIfNull(timeZone, TimeZone::getDefault);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/time/package-info.java b/src/main/java/org/apache/commons/lang3/time/package-info.java
new file mode 100644
index 000000000..2529b9827
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides classes and methods to work with dates and durations.
+ * These classes are immutable (and therefore thread-safe) apart from {@link org.apache.commons.lang3.time.StopWatch}.
+ *
+ * <p>The time package contains some basic utilities for manipulating time (a delorean, police box and grandfather clock?).
+ * These include a {@link org.apache.commons.lang3.time.StopWatch} for simple performance measurements and an optimised {@link org.apache.commons.lang3.time.FastDateFormat} class.</p>
+ *
+ * <p>New in Lang 2.1 is the {@link org.apache.commons.lang3.time.DurationFormatUtils} class, which provides various methods for formatting durations.</p>
+ *
+ * @since 2.0
+ */
+package org.apache.commons.lang3.time;
diff --git a/src/main/java/org/apache/commons/lang3/tuple/ImmutablePair.java b/src/main/java/org/apache/commons/lang3/tuple/ImmutablePair.java
new file mode 100644
index 000000000..6caf3c52b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/ImmutablePair.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * An immutable pair consisting of two {@link Object} elements.
+ *
+ * <p>Although the implementation is immutable, there is no restriction on the objects
+ * that may be stored. If mutable objects are stored in the pair, then the pair
+ * itself effectively becomes mutable. The class is also {@code final}, so a subclass
+ * can not add undesirable behavior.</p>
+ *
+ * <p>#ThreadSafe# if both paired objects are thread-safe</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since 3.0
+ */
+public class ImmutablePair<L, R> extends Pair<L, R> {
+
+ /**
+ * An empty array.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final ImmutablePair<?, ?>[] EMPTY_ARRAY = {};
+
+ /**
+ * An immutable pair of nulls.
+ */
+ // This is not defined with generics to avoid warnings in call sites.
+ @SuppressWarnings("rawtypes")
+ private static final ImmutablePair NULL = new ImmutablePair<>(null, null);
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, R> ImmutablePair<L, R>[] emptyArray() {
+ return (ImmutablePair<L, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Creates an immutable pair of two objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @return a pair formed from the two parameters, not null
+ * @since 3.11
+ */
+ public static <L, R> Pair<L, R> left(final L left) {
+ return ImmutablePair.of(left, null);
+ }
+
+ /**
+ * Returns an immutable pair of nulls.
+ *
+ * @param <L> the left element of this pair. Value is {@code null}.
+ * @param <R> the right element of this pair. Value is {@code null}.
+ * @return an immutable pair of nulls.
+ * @since 3.6
+ */
+ public static <L, R> ImmutablePair<L, R> nullPair() {
+ return NULL;
+ }
+
+ /**
+ * Creates an immutable pair of two objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> ImmutablePair<L, R> of(final L left, final R right) {
+ return left != null || right != null ? new ImmutablePair<>(left, right) : nullPair();
+ }
+
+ /**
+ * Creates an immutable pair from a map entry.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param pair the existing map entry.
+ * @return a pair formed from the map entry
+ * @since 3.10
+ */
+ public static <L, R> ImmutablePair<L, R> of(final Map.Entry<L, R> pair) {
+ return pair != null ? new ImmutablePair<>(pair.getKey(), pair.getValue()) : nullPair();
+ }
+
+ /**
+ * Creates an immutable pair of two non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param right the right element, may not be null
+ * @return a pair formed from the two parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, R> ImmutablePair<L, R> ofNonNull(final L left, final R right) {
+ return of(Objects.requireNonNull(left, "left"), Objects.requireNonNull(right, "right"));
+ }
+
+ /**
+ * Creates an immutable pair of two objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ * @since 3.11
+ */
+ public static <L, R> Pair<L, R> right(final R right) {
+ return ImmutablePair.of(null, right);
+ }
+
+ /** Left object */
+ public final L left;
+
+ /** Right object */
+ public final R right;
+
+ /**
+ * Create a new pair instance.
+ *
+ * @param left the left value, may be null
+ * @param right the right value, may be null
+ */
+ public ImmutablePair(final L left, final R right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * Throws {@link UnsupportedOperationException}.
+ *
+ * <p>This pair is immutable, so this operation is not supported.</p>
+ *
+ * @param value the value to set
+ * @return never
+ * @throws UnsupportedOperationException as this operation is not supported
+ */
+ @Override
+ public R setValue(final R value) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/tuple/ImmutableTriple.java b/src/main/java/org/apache/commons/lang3/tuple/ImmutableTriple.java
new file mode 100644
index 000000000..453d08a8a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/ImmutableTriple.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.util.Objects;
+
+/**
+ * An immutable triple consisting of three {@link Object} elements.
+ *
+ * <p>Although the implementation is immutable, there is no restriction on the objects
+ * that may be stored. If mutable objects are stored in the triple, then the triple
+ * itself effectively becomes mutable. The class is also {@code final}, so a subclass
+ * can not add undesirable behavior.</p>
+ *
+ * <p>#ThreadSafe# if all three objects are thread-safe</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ *
+ * @since 3.2
+ */
+public class ImmutableTriple<L, M, R> extends Triple<L, M, R> {
+
+ /**
+ * An empty array.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final ImmutableTriple<?, ?, ?>[] EMPTY_ARRAY = {};
+
+ /**
+ * An immutable triple of nulls.
+ */
+ // This is not defined with generics to avoid warnings in call sites.
+ @SuppressWarnings("rawtypes")
+ private static final ImmutableTriple NULL = new ImmutableTriple<>(null, null, null);
+
+ /** Serialization version */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, M, R> ImmutableTriple<L, M, R>[] emptyArray() {
+ return (ImmutableTriple<L, M, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Returns an immutable triple of nulls.
+ *
+ * @param <L> the left element of this triple. Value is {@code null}.
+ * @param <M> the middle element of this triple. Value is {@code null}.
+ * @param <R> the right element of this triple. Value is {@code null}.
+ * @return an immutable triple of nulls.
+ * @since 3.6
+ */
+ public static <L, M, R> ImmutableTriple<L, M, R> nullTriple() {
+ return NULL;
+ }
+
+ /**
+ * Obtains an immutable triple of three objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param middle the middle element, may be null
+ * @param right the right element, may be null
+ * @return a triple formed from the three parameters, not null
+ */
+ public static <L, M, R> ImmutableTriple<L, M, R> of(final L left, final M middle, final R right) {
+ return left != null | middle != null || right != null ? new ImmutableTriple<>(left, middle, right) : nullTriple();
+ }
+
+ /**
+ * Obtains an immutable triple of three non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param middle the middle element, may not be null
+ * @param right the right element, may not be null
+ * @return a triple formed from the three parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, M, R> ImmutableTriple<L, M, R> ofNonNull(final L left, final M middle, final R right) {
+ return of(Objects.requireNonNull(left, "left"), Objects.requireNonNull(middle, "middle"), Objects.requireNonNull(right, "right"));
+ }
+
+ /** Left object */
+ public final L left;
+ /** Middle object */
+ public final M middle;
+
+ /** Right object */
+ public final R right;
+
+ /**
+ * Create a new triple instance.
+ *
+ * @param left the left value, may be null
+ * @param middle the middle value, may be null
+ * @param right the right value, may be null
+ */
+ public ImmutableTriple(final L left, final M middle, final R right) {
+ this.left = left;
+ this.middle = middle;
+ this.right = right;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public M getMiddle() {
+ return middle;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+}
+
diff --git a/src/main/java/org/apache/commons/lang3/tuple/MutablePair.java b/src/main/java/org/apache/commons/lang3/tuple/MutablePair.java
new file mode 100644
index 000000000..f4b5512da
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/MutablePair.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A mutable pair consisting of two {@link Object} elements.
+ *
+ * <p>Not #ThreadSafe#</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since 3.0
+ */
+public class MutablePair<L, R> extends Pair<L, R> {
+
+ /**
+ * An empty array.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final MutablePair<?, ?>[] EMPTY_ARRAY = {};
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, R> MutablePair<L, R>[] emptyArray() {
+ return (MutablePair<L, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Creates a mutable pair of two objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> MutablePair<L, R> of(final L left, final R right) {
+ return new MutablePair<>(left, right);
+ }
+
+ /**
+ * Creates a mutable pair from a map entry.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param pair the existing map entry.
+ * @return a pair formed from the map entry
+ */
+ public static <L, R> MutablePair<L, R> of(final Map.Entry<L, R> pair) {
+ final L left;
+ final R right;
+ if (pair != null) {
+ left = pair.getKey();
+ right = pair.getValue();
+ } else {
+ left = null;
+ right = null;
+ }
+ return new MutablePair<>(left, right);
+ }
+
+ /**
+ * Creates a mutable pair of two non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param right the right element, may not be null
+ * @return a pair formed from the two parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, R> MutablePair<L, R> ofNonNull(final L left, final R right) {
+ return of(Objects.requireNonNull(left, "left"), Objects.requireNonNull(right, "right"));
+ }
+
+ /** Left object */
+ public L left;
+
+ /** Right object */
+ public R right;
+
+ /**
+ * Create a new pair instance of two nulls.
+ */
+ public MutablePair() {
+ }
+
+ /**
+ * Create a new pair instance.
+ *
+ * @param left the left value, may be null
+ * @param right the right value, may be null
+ */
+ public MutablePair(final L left, final R right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * Sets the left element of the pair.
+ *
+ * @param left the new value of the left element, may be null
+ */
+ public void setLeft(final L left) {
+ this.left = left;
+ }
+
+ /**
+ * Sets the right element of the pair.
+ *
+ * @param right the new value of the right element, may be null
+ */
+ public void setRight(final R right) {
+ this.right = right;
+ }
+
+ /**
+ * Sets the {@code Map.Entry} value.
+ * This sets the right element of the pair.
+ *
+ * @param value the right value to set, not null
+ * @return the old value for the right element
+ */
+ @Override
+ public R setValue(final R value) {
+ final R result = getRight();
+ setRight(value);
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/tuple/MutableTriple.java b/src/main/java/org/apache/commons/lang3/tuple/MutableTriple.java
new file mode 100644
index 000000000..b446017c4
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/MutableTriple.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.util.Objects;
+
+/**
+ * A mutable triple consisting of three {@link Object} elements.
+ *
+ * <p>Not #ThreadSafe#</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ *
+ * @since 3.2
+ */
+public class MutableTriple<L, M, R> extends Triple<L, M, R> {
+
+ /**
+ * The empty array singleton.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final MutableTriple<?, ?, ?>[] EMPTY_ARRAY = {};
+
+ /** Serialization version */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, M, R> MutableTriple<L, M, R>[] emptyArray() {
+ return (MutableTriple<L, M, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Obtains a mutable triple of three objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param middle the middle element, may be null
+ * @param right the right element, may be null
+ * @return a triple formed from the three parameters, not null
+ */
+ public static <L, M, R> MutableTriple<L, M, R> of(final L left, final M middle, final R right) {
+ return new MutableTriple<>(left, middle, right);
+ }
+
+ /**
+ * Obtains a mutable triple of three non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param middle the middle element, may not be null
+ * @param right the right element, may not be null
+ * @return a triple formed from the three parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, M, R> MutableTriple<L, M, R> ofNonNull(final L left, final M middle, final R right) {
+ return of(Objects.requireNonNull(left, "left"), Objects.requireNonNull(middle, "middle"), Objects.requireNonNull(right, "right"));
+ }
+
+ /** Left object */
+ public L left;
+ /** Middle object */
+ public M middle;
+
+ /** Right object */
+ public R right;
+
+ /**
+ * Create a new triple instance of three nulls.
+ */
+ public MutableTriple() {
+ }
+
+ /**
+ * Create a new triple instance.
+ *
+ * @param left the left value, may be null
+ * @param middle the middle value, may be null
+ * @param right the right value, may be null
+ */
+ public MutableTriple(final L left, final M middle, final R right) {
+ this.left = left;
+ this.middle = middle;
+ this.right = right;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public L getLeft() {
+ return left;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public M getMiddle() {
+ return middle;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public R getRight() {
+ return right;
+ }
+
+ /**
+ * Sets the left element of the triple.
+ *
+ * @param left the new value of the left element, may be null
+ */
+ public void setLeft(final L left) {
+ this.left = left;
+ }
+
+ /**
+ * Sets the middle element of the triple.
+ *
+ * @param middle the new value of the middle element, may be null
+ */
+ public void setMiddle(final M middle) {
+ this.middle = middle;
+ }
+
+ /**
+ * Sets the right element of the triple.
+ *
+ * @param right the new value of the right element, may be null
+ */
+ public void setRight(final R right) {
+ this.right = right;
+ }
+}
+
diff --git a/src/main/java/org/apache/commons/lang3/tuple/Pair.java b/src/main/java/org/apache/commons/lang3/tuple/Pair.java
new file mode 100644
index 000000000..3b97e061b
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/Pair.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.builder.CompareToBuilder;
+
+/**
+ * A pair consisting of two elements.
+ *
+ * <p>This class is an abstract implementation defining the basic API.
+ * It refers to the elements as 'left' and 'right'. It also implements the
+ * {@code Map.Entry} interface where the key is 'left' and the value is 'right'.</p>
+ *
+ * <p>Subclass implementations may be mutable or immutable.
+ * However, there is no restriction on the type of the stored objects that may be stored.
+ * If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ *
+ * @since 3.0
+ */
+public abstract class Pair<L, R> implements Map.Entry<L, R>, Comparable<Pair<L, R>>, Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 4954918890077093841L;
+
+ /**
+ * An empty array.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final Pair<?, ?>[] EMPTY_ARRAY = {};
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, R> Pair<L, R>[] emptyArray() {
+ return (Pair<L, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Creates an immutable pair of two objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param right the right element, may be null
+ * @return a pair formed from the two parameters, not null
+ */
+ public static <L, R> Pair<L, R> of(final L left, final R right) {
+ return ImmutablePair.of(left, right);
+ }
+
+ /**
+ * Creates an immutable pair from a map entry.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param pair the map entry.
+ * @return a pair formed from the map entry
+ * @since 3.10
+ */
+ public static <L, R> Pair<L, R> of(final Map.Entry<L, R> pair) {
+ return ImmutablePair.of(pair);
+ }
+
+ /**
+ * Creates an immutable pair of two non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the pair to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param right the right element, may not be null
+ * @return a pair formed from the two parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, R> Pair<L, R> ofNonNull(final L left, final R right) {
+ return ImmutablePair.ofNonNull(left, right);
+ }
+
+ /**
+ * Compares the pair based on the left element followed by the right element.
+ * The types must be {@link Comparable}.
+ *
+ * @param other the other pair, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ @Override
+ public int compareTo(final Pair<L, R> other) {
+ return new CompareToBuilder().append(getLeft(), other.getLeft())
+ .append(getRight(), other.getRight()).toComparison();
+ }
+
+ /**
+ * Compares this pair to another based on the two elements.
+ *
+ * @param obj the object to compare to, null returns false
+ * @return true if the elements of the pair are equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map.Entry<?, ?>) {
+ final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
+ return Objects.equals(getKey(), other.getKey())
+ && Objects.equals(getValue(), other.getValue());
+ }
+ return false;
+ }
+
+ /**
+ * Gets the key from this pair.
+ *
+ * <p>This method implements the {@code Map.Entry} interface returning the
+ * left element as the key.</p>
+ *
+ * @return the left element as the key, may be null
+ */
+ @Override
+ public final L getKey() {
+ return getLeft();
+ }
+
+ /**
+ * Gets the left element from this pair.
+ *
+ * <p>When treated as a key-value pair, this is the key.</p>
+ *
+ * @return the left element, may be null
+ */
+ public abstract L getLeft();
+
+ /**
+ * Gets the right element from this pair.
+ *
+ * <p>When treated as a key-value pair, this is the value.</p>
+ *
+ * @return the right element, may be null
+ */
+ public abstract R getRight();
+
+ /**
+ * Gets the value from this pair.
+ *
+ * <p>This method implements the {@code Map.Entry} interface returning the
+ * right element as the value.</p>
+ *
+ * @return the right element as the value, may be null
+ */
+ @Override
+ public R getValue() {
+ return getRight();
+ }
+
+ /**
+ * Returns a suitable hash code.
+ * The hash code follows the definition in {@code Map.Entry}.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ // see Map.Entry API specification
+ return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
+ }
+
+ /**
+ * Returns a String representation of this pair using the format {@code ($left,$right)}.
+ *
+ * @return a string describing this object, not null
+ */
+ @Override
+ public String toString() {
+ return "(" + getLeft() + ',' + getRight() + ')';
+ }
+
+ /**
+ * Formats the receiver using the given format.
+ *
+ * <p>This uses {@link java.util.Formattable} to perform the formatting. Two variables may
+ * be used to embed the left and right elements. Use {@code %1$s} for the left
+ * element (key) and {@code %2$s} for the right element (value).
+ * The default format used by {@code toString()} is {@code (%1$s,%2$s)}.</p>
+ *
+ * @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null
+ * @return the formatted string, not null
+ */
+ public String toString(final String format) {
+ return String.format(format, getLeft(), getRight());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/tuple/Triple.java b/src/main/java/org/apache/commons/lang3/tuple/Triple.java
new file mode 100644
index 000000000..a7433ea60
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/Triple.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.tuple;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import org.apache.commons.lang3.builder.CompareToBuilder;
+
+/**
+ * A triple consisting of three elements.
+ *
+ * <p>This class is an abstract implementation defining the basic API.
+ * It refers to the elements as 'left', 'middle' and 'right'.</p>
+ *
+ * <p>Subclass implementations may be mutable or immutable.
+ * However, there is no restriction on the type of the stored objects that may be stored.
+ * If mutable objects are stored in the triple, then the triple itself effectively becomes mutable.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ *
+ * @since 3.2
+ */
+public abstract class Triple<L, M, R> implements Comparable<Triple<L, M, R>>, Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * An empty array.
+ * <p>
+ * Consider using {@link #emptyArray()} to avoid generics warnings.
+ * </p>
+ *
+ * @since 3.10.
+ */
+ public static final Triple<?, ?, ?>[] EMPTY_ARRAY = {};
+
+ /**
+ * Returns the empty array singleton that can be assigned without compiler warning.
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @return the empty array singleton that can be assigned without compiler warning.
+ *
+ * @since 3.10.
+ */
+ @SuppressWarnings("unchecked")
+ public static <L, M, R> Triple<L, M, R>[] emptyArray() {
+ return (Triple<L, M, R>[]) EMPTY_ARRAY;
+ }
+
+ /**
+ * Obtains an immutable triple of three objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may be null
+ * @param middle the middle element, may be null
+ * @param right the right element, may be null
+ * @return a triple formed from the three parameters, not null
+ */
+ public static <L, M, R> Triple<L, M, R> of(final L left, final M middle, final R right) {
+ return ImmutableTriple.of(left, middle, right);
+ }
+
+ /**
+ * Obtains an immutable triple of three non-null objects inferring the generic types.
+ *
+ * <p>This factory allows the triple to be created using inference to
+ * obtain the generic types.</p>
+ *
+ * @param <L> the left element type
+ * @param <M> the middle element type
+ * @param <R> the right element type
+ * @param left the left element, may not be null
+ * @param middle the middle element, may not be null
+ * @param right the right element, may not be null
+ * @return a triple formed from the three parameters, not null
+ * @throws NullPointerException if any input is null
+ * @since 3.13.0
+ */
+ public static <L, M, R> Triple<L, M, R> ofNonNull(final L left, final M middle, final R right) {
+ return ImmutableTriple.ofNonNull(left, middle, right);
+ }
+
+ /**
+ * Compares the triple based on the left element, followed by the middle element,
+ * finally the right element.
+ * The types must be {@link Comparable}.
+ *
+ * @param other the other triple, not null
+ * @return negative if this is less, zero if equal, positive if greater
+ */
+ @Override
+ public int compareTo(final Triple<L, M, R> other) {
+ return new CompareToBuilder().append(getLeft(), other.getLeft())
+ .append(getMiddle(), other.getMiddle())
+ .append(getRight(), other.getRight()).toComparison();
+ }
+
+ /**
+ * Compares this triple to another based on the three elements.
+ *
+ * @param obj the object to compare to, null returns false
+ * @return true if the elements of the triple are equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Triple<?, ?, ?>) {
+ final Triple<?, ?, ?> other = (Triple<?, ?, ?>) obj;
+ return Objects.equals(getLeft(), other.getLeft())
+ && Objects.equals(getMiddle(), other.getMiddle())
+ && Objects.equals(getRight(), other.getRight());
+ }
+ return false;
+ }
+
+ /**
+ * Gets the left element from this triple.
+ *
+ * @return the left element, may be null
+ */
+ public abstract L getLeft();
+
+ /**
+ * Gets the middle element from this triple.
+ *
+ * @return the middle element, may be null
+ */
+ public abstract M getMiddle();
+
+ /**
+ * Gets the right element from this triple.
+ *
+ * @return the right element, may be null
+ */
+ public abstract R getRight();
+
+ /**
+ * Returns a suitable hash code.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getLeft()) ^ Objects.hashCode(getMiddle()) ^ Objects.hashCode(getRight());
+ }
+
+ /**
+ * Returns a String representation of this triple using the format {@code ($left,$middle,$right)}.
+ *
+ * @return a string describing this object, not null
+ */
+ @Override
+ public String toString() {
+ return "(" + getLeft() + "," + getMiddle() + "," + getRight() + ")";
+ }
+
+ /**
+ * Formats the receiver using the given format.
+ *
+ * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
+ * be used to embed the left and right elements. Use {@code %1$s} for the left
+ * element, {@code %2$s} for the middle and {@code %3$s} for the right element.
+ * The default format used by {@code toString()} is {@code (%1$s,%2$s,%3$s)}.</p>
+ *
+ * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null
+ * @return the formatted string, not null
+ */
+ public String toString(final String format) {
+ return String.format(format, getLeft(), getMiddle(), getRight());
+ }
+
+}
+
diff --git a/src/main/java/org/apache/commons/lang3/tuple/package-info.java b/src/main/java/org/apache/commons/lang3/tuple/package-info.java
new file mode 100644
index 000000000..b58034460
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/tuple/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Tuple classes, starting with a Pair class in version 3.0.
+ *
+ * @since 3.0
+ */
+package org.apache.commons.lang3.tuple;
diff --git a/src/main/java/org/apache/commons/lang3/util/FluentBitSet.java b/src/main/java/org/apache/commons/lang3/util/FluentBitSet.java
new file mode 100644
index 000000000..98e3cbcb8
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/util/FluentBitSet.java
@@ -0,0 +1,609 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.util;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * A fluent {@link BitSet} with additional operations.
+ * <p>
+ * Originally from Apache Commons VFS with more added to act as a fluent replacement for {@link java.util.BitSet}.
+ * </p>
+ * @since 3.13.0
+ */
+public final class FluentBitSet implements Cloneable, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Working BitSet.
+ */
+ private final BitSet bitSet;
+
+ /**
+ * Creates a new bit set. All bits are initially {@code false}.
+ */
+ public FluentBitSet() {
+ this(new BitSet());
+ }
+
+ /**
+ * Creates a new instance for the given bit set.
+ *
+ * @param set The bit set to wrap.
+ */
+ public FluentBitSet(final BitSet set) {
+ this.bitSet = Objects.requireNonNull(set, "set");
+ }
+
+ /**
+ * Creates a bit set whose initial size is large enough to explicitly represent bits with indices in the range {@code 0}
+ * through {@code nbits-1}. All bits are initially {@code false}.
+ *
+ * @param nbits the initial size of the bit set.
+ * @throws NegativeArraySizeException if the specified initial size is negative.
+ */
+ public FluentBitSet(final int nbits) {
+ this(new BitSet(nbits));
+ }
+
+ /**
+ * Performs a logical <b>AND</b> of this target bit set with the argument bit set. This bit set is modified so that each
+ * bit in it has the value {@code true} if and only if it both initially had the value {@code true} and the
+ * corresponding bit in the bit set argument also had the value {@code true}.
+ *
+ * @param set a bit set.
+ * @return this.
+ */
+ public FluentBitSet and(final BitSet set) {
+ bitSet.and(set);
+ return this;
+ }
+
+ /**
+ * Performs a logical <b>AND</b> of this target bit set with the argument bit set. This bit set is modified so that each
+ * bit in it has the value {@code true} if and only if it both initially had the value {@code true} and the
+ * corresponding bit in the bit set argument also had the value {@code true}.
+ *
+ * @param set a bit set.
+ * @return this.
+ */
+ public FluentBitSet and(final FluentBitSet set) {
+ bitSet.and(set.bitSet);
+ return this;
+ }
+
+ /**
+ * Clears all of the bits in this {@link BitSet} whose corresponding bit is set in the specified {@link BitSet}.
+ *
+ * @param set the {@link BitSet} with which to mask this {@link BitSet}.
+ * @return this.
+ */
+ public FluentBitSet andNot(final BitSet set) {
+ bitSet.andNot(set);
+ return this;
+ }
+
+ /**
+ * Clears all of the bits in this {@link BitSet} whose corresponding bit is set in the specified {@link BitSet}.
+ *
+ * @param set the {@link BitSet} with which to mask this {@link BitSet}.
+ * @return this.
+ */
+ public FluentBitSet andNot(final FluentBitSet set) {
+ this.bitSet.andNot(set.bitSet);
+ return this;
+ }
+
+ /**
+ * Gets the wrapped bit set.
+ *
+ * @return the wrapped bit set.
+ */
+ public BitSet bitSet() {
+ return bitSet;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@link BitSet}.
+ *
+ * @return the number of bits set to {@code true} in this {@link BitSet}.
+ */
+ public int cardinality() {
+ return bitSet.cardinality();
+ }
+
+ /**
+ * Sets all of the bits in this BitSet to {@code false}.
+ *
+ * @return this.
+ */
+ public FluentBitSet clear() {
+ bitSet.clear();
+ return this;
+ }
+
+ /**
+ * Sets the bits specified by the indexes to {@code false}.
+ *
+ * @param bitIndexArray the index of the bit to be cleared.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ * @return this.
+ */
+ public FluentBitSet clear(final int... bitIndexArray) {
+ for (final int e : bitIndexArray) {
+ this.bitSet.clear(e);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the bit specified by the index to {@code false}.
+ *
+ * @param bitIndex the index of the bit to be cleared.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ * @return this.
+ */
+ public FluentBitSet clear(final int bitIndex) {
+ bitSet.clear(bitIndex);
+ return this;
+ }
+
+ /**
+ * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to
+ * {@code false}.
+ *
+ * @param fromIndex index of the first bit to be cleared.
+ * @param toIndex index after the last bit to be cleared.
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}.
+ * @return this.
+ */
+ public FluentBitSet clear(final int fromIndex, final int toIndex) {
+ bitSet.clear(fromIndex, toIndex);
+ return this;
+ }
+
+ /**
+ * Cloning this {@link BitSet} produces a new {@link BitSet} that is equal to it. The clone of the bit set is another
+ * bit set that has exactly the same bits set to {@code true} as this bit set.
+ *
+ * @return a clone of this bit set
+ * @see #size()
+ */
+ @Override
+ public Object clone() {
+ return new FluentBitSet((BitSet) bitSet.clone());
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FluentBitSet)) {
+ return false;
+ }
+ final FluentBitSet other = (FluentBitSet) obj;
+ return Objects.equals(bitSet, other.bitSet);
+ }
+
+ /**
+ * Sets the bit at the specified index to the complement of its current value.
+ *
+ * @param bitIndex the index of the bit to flip.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ * @return this.
+ */
+ public FluentBitSet flip(final int bitIndex) {
+ bitSet.flip(bitIndex);
+ return this;
+ }
+
+ /**
+ * Sets each bit from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to the
+ * complement of its current value.
+ *
+ * @param fromIndex index of the first bit to flip.
+ * @param toIndex index after the last bit to flip.
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}.
+ * @return this.
+ */
+ public FluentBitSet flip(final int fromIndex, final int toIndex) {
+ bitSet.flip(fromIndex, toIndex);
+ return this;
+ }
+
+ /**
+ * Returns the value of the bit with the specified index. The value is {@code true} if the bit with the index
+ * {@code bitIndex} is currently set in this {@link BitSet}; otherwise, the result is {@code false}.
+ *
+ * @param bitIndex the bit index.
+ * @return the value of the bit with the specified index.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ */
+ public boolean get(final int bitIndex) {
+ return bitSet.get(bitIndex);
+ }
+
+ /**
+ * Returns a new {@link BitSet} composed of bits from this {@link BitSet} from {@code fromIndex} (inclusive) to
+ * {@code toIndex} (exclusive).
+ *
+ * @param fromIndex index of the first bit to include.
+ * @param toIndex index after the last bit to include.
+ * @return a new {@link BitSet} from a range of this {@link BitSet}.
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}.
+ */
+ public FluentBitSet get(final int fromIndex, final int toIndex) {
+ return new FluentBitSet(bitSet.get(fromIndex, toIndex));
+ }
+
+ @Override
+ public int hashCode() {
+ return bitSet.hashCode();
+ }
+
+ /**
+ * Returns true if the specified {@link BitSet} has any bits set to {@code true} that are also set to {@code true} in
+ * this {@link BitSet}.
+ *
+ * @param set {@link BitSet} to intersect with.
+ * @return boolean indicating whether this {@link BitSet} intersects the specified {@link BitSet}.
+ */
+ public boolean intersects(final BitSet set) {
+ return bitSet.intersects(set);
+ }
+
+ /**
+ * Returns true if the specified {@link BitSet} has any bits set to {@code true} that are also set to {@code true} in
+ * this {@link BitSet}.
+ *
+ * @param set {@link BitSet} to intersect with.
+ * @return boolean indicating whether this {@link BitSet} intersects the specified {@link BitSet}.
+ */
+ public boolean intersects(final FluentBitSet set) {
+ return bitSet.intersects(set.bitSet);
+ }
+
+ /**
+ * Returns true if this {@link BitSet} contains no bits that are set to {@code true}.
+ *
+ * @return boolean indicating whether this {@link BitSet} is empty.
+ */
+ public boolean isEmpty() {
+ return bitSet.isEmpty();
+ }
+
+ /**
+ * Returns the "logical size" of this {@link BitSet}: the index of the highest set bit in the {@link BitSet} plus one.
+ * Returns zero if the {@link BitSet} contains no set bits.
+ *
+ * @return the logical size of this {@link BitSet}.
+ */
+ public int length() {
+ return bitSet.length();
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code false} that occurs on or after the specified starting index.
+ *
+ * @param fromIndex the index to start checking from (inclusive).
+ * @return the index of the next clear bit.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ */
+ public int nextClearBit(final int fromIndex) {
+ return bitSet.nextClearBit(fromIndex);
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code true} that occurs on or after the specified starting index.
+ * If no such bit exists then {@code -1} is returned.
+ * <p>
+ * To iterate over the {@code true} bits in a {@link BitSet}, use the following loop:
+ * </p>
+ *
+ * <pre>
+ * {@code
+ * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+ * // operate on index i here
+ * if (i == Integer.MAX_VALUE) {
+ * break; // or (i+1) would overflow
+ * }
+ * }}
+ * </pre>
+ *
+ * @param fromIndex the index to start checking from (inclusive).
+ * @return the index of the next set bit, or {@code -1} if there is no such bit.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ */
+ public int nextSetBit(final int fromIndex) {
+ return bitSet.nextSetBit(fromIndex);
+ }
+
+ /**
+ * Performs a logical <b>OR</b> of this bit set with the bit set argument. This bit set is modified so that a bit in it
+ * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in
+ * the bit set argument has the value {@code true}.
+ *
+ * @param set a bit set.
+ * @return this.
+ */
+ public FluentBitSet or(final BitSet set) {
+ bitSet.or(set);
+ return this;
+ }
+
+ /**
+ * Performs a logical <b>OR</b> of this bit set with the bit set arguments. This bit set is modified so that a bit in it
+ * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in
+ * the bit set argument has the value {@code true}.
+ *
+ * @param set a bit set.
+ * @return this.
+ */
+ public FluentBitSet or(final FluentBitSet... set) {
+ for (final FluentBitSet e : set) {
+ this.bitSet.or(e.bitSet);
+ }
+ return this;
+ }
+
+ /**
+ * Performs a logical <b>OR</b> of this bit set with the bit set argument. This bit set is modified so that a bit in it
+ * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in
+ * the bit set argument has the value {@code true}.
+ *
+ * @param set a bit set.
+ * @return this.
+ */
+ public FluentBitSet or(final FluentBitSet set) {
+ this.bitSet.or(set.bitSet);
+ return this;
+ }
+
+ /**
+ * Returns the index of the nearest bit that is set to {@code false} that occurs on or before the specified starting
+ * index. If no such bit exists, or if {@code -1} is given as the starting index, then {@code -1} is returned.
+ *
+ * @param fromIndex the index to start checking from (inclusive).
+ * @return the index of the previous clear bit, or {@code -1} if there is no such bit.
+ * @throws IndexOutOfBoundsException if the specified index is less than {@code -1}.
+ */
+ public int previousClearBit(final int fromIndex) {
+ return bitSet.previousClearBit(fromIndex);
+ }
+
+ /**
+ * Returns the index of the nearest bit that is set to {@code true} that occurs on or before the specified starting
+ * index. If no such bit exists, or if {@code -1} is given as the starting index, then {@code -1} is returned.
+ *
+ * <p>
+ * To iterate over the {@code true} bits in a {@link BitSet}, use the following loop:
+ *
+ * <pre>
+ * {@code
+ * for (int i = bs.length(); (i = bs.previousSetBit(i-1)) >= 0; ) {
+ * // operate on index i here
+ * }}
+ * </pre>
+ *
+ * @param fromIndex the index to start checking from (inclusive)
+ * @return the index of the previous set bit, or {@code -1} if there is no such bit
+ * @throws IndexOutOfBoundsException if the specified index is less than {@code -1}
+ */
+ public int previousSetBit(final int fromIndex) {
+ return bitSet.previousSetBit(fromIndex);
+ }
+
+ /**
+ * Sets the bit at the specified indexes to {@code true}.
+ *
+ * @param bitIndexArray a bit index array.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ * @return this.
+ */
+ public FluentBitSet set(final int... bitIndexArray) {
+ for (final int e : bitIndexArray) {
+ bitSet.set(e);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the bit at the specified index to {@code true}.
+ *
+ * @param bitIndex a bit index
+ * @throws IndexOutOfBoundsException if the specified index is negative
+ * @return this.
+ */
+ public FluentBitSet set(final int bitIndex) {
+ bitSet.set(bitIndex);
+ return this;
+ }
+
+ /**
+ * Sets the bit at the specified index to the specified value.
+ *
+ * @param bitIndex a bit index.
+ * @param value a boolean value to set.
+ * @throws IndexOutOfBoundsException if the specified index is negative.
+ * @return this.
+ */
+ public FluentBitSet set(final int bitIndex, final boolean value) {
+ bitSet.set(bitIndex, value);
+ return this;
+ }
+
+ /**
+ * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to
+ * {@code true}.
+ *
+ * @param fromIndex index of the first bit to be set.
+ * @param toIndex index after the last bit to be set.
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}.
+ * @return this.
+ */
+ public FluentBitSet set(final int fromIndex, final int toIndex) {
+ bitSet.set(fromIndex, toIndex);
+ return this;
+ }
+
+ /**
+ * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to the
+ * specified value.
+ *
+ * @param fromIndex index of the first bit to be set.
+ * @param toIndex index after the last bit to be set.
+ * @param value value to set the selected bits to.
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}.
+ * @return this.
+ */
+ public FluentBitSet set(final int fromIndex, final int toIndex, final boolean value) {
+ bitSet.set(fromIndex, toIndex, value);
+ return this;
+ }
+
+ /**
+ * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to
+ * {@code true}.
+ *
+ * @param fromIndex index of the first bit to be set
+ * @param toIndex index of the last bit to be set
+ * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or
+ * {@code fromIndex} is larger than {@code toIndex}
+ * @return this.
+ */
+ public FluentBitSet setInclusive(final int fromIndex, final int toIndex) {
+ bitSet.set(fromIndex, toIndex + 1);
+ return this;
+ }
+
+ /**
+ * Returns the number of bits of space actually in use by this {@link BitSet} to represent bit values. The maximum
+ * element in the set is the size - 1st element.
+ *
+ * @return the number of bits currently in this bit set.
+ */
+ public int size() {
+ return bitSet.size();
+ }
+
+ /**
+ * Returns a stream of indices for which this {@link BitSet} contains a bit in the set state. The indices are returned
+ * in order, from lowest to highest. The size of the stream is the number of bits in the set state, equal to the value
+ * returned by the {@link #cardinality()} method.
+ *
+ * <p>
+ * The bit set must remain constant during the execution of the terminal stream operation. Otherwise, the result of the
+ * terminal stream operation is undefined.
+ * </p>
+ *
+ * @return a stream of integers representing set indices.
+ * @since 1.8
+ */
+ public IntStream stream() {
+ return bitSet.stream();
+ }
+
+ /**
+ * Returns a new byte array containing all the bits in this bit set.
+ *
+ * <p>
+ * More precisely, if:
+ * </p>
+ * <ol>
+ * <li>{@code byte[] bytes = s.toByteArray();}</li>
+ * <li>then {@code bytes.length == (s.length()+7)/8} and</li>
+ * <li>{@code s.get(n) == ((bytes[n/8] & (1<<(n%8))) != 0)}</li>
+ * <li>for all {@code n < 8 * bytes.length}.</li>
+ * </ol>
+ *
+ * @return a byte array containing a little-endian representation of all the bits in this bit set
+ */
+ public byte[] toByteArray() {
+ return bitSet.toByteArray();
+ }
+
+ /**
+ * Returns a new byte array containing all the bits in this bit set.
+ *
+ * <p>
+ * More precisely, if:
+ * </p>
+ * <ol>
+ * <li>{@code long[] longs = s.toLongArray();}</li>
+ * <li>then {@code longs.length == (s.length()+63)/64} and</li>
+ * <li>{@code s.get(n) == ((longs[n/64] & (1L<<(n%64))) != 0)}</li>
+ * <li>for all {@code n < 64 * longs.length}.</li>
+ * </ol>
+ *
+ * @return a byte array containing a little-endian representation of all the bits in this bit set
+ */
+ public long[] toLongArray() {
+ return bitSet.toLongArray();
+ }
+
+ @Override
+ public String toString() {
+ return bitSet.toString();
+ }
+
+ /**
+ * Performs a logical <b>XOR</b> of this bit set with the bit set argument. This bit set is modified so that a bit in it
+ * has the value {@code true} if and only if one of the following statements holds:
+ * <ul>
+ * <li>The bit initially has the value {@code true}, and the corresponding bit in the argument has the value
+ * {@code false}.
+ * <li>The bit initially has the value {@code false}, and the corresponding bit in the argument has the value
+ * {@code true}.
+ * </ul>
+ *
+ * @param set a bit set
+ * @return this.
+ */
+ public FluentBitSet xor(final BitSet set) {
+ bitSet.xor(set);
+ return this;
+ }
+
+ /**
+ * Performs a logical <b>XOR</b> of this bit set with the bit set argument. This bit set is modified so that a bit in it
+ * has the value {@code true} if and only if one of the following statements holds:
+ * <ul>
+ * <li>The bit initially has the value {@code true}, and the corresponding bit in the argument has the value
+ * {@code false}.
+ * <li>The bit initially has the value {@code false}, and the corresponding bit in the argument has the value
+ * {@code true}.
+ * </ul>
+ *
+ * @param set a bit set
+ * @return this.
+ */
+ public FluentBitSet xor(final FluentBitSet set) {
+ bitSet.xor(set.bitSet);
+ return this;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/lang3/util/package-info.java b/src/main/java/org/apache/commons/lang3/util/package-info.java
new file mode 100644
index 000000000..cd773b0cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Provides classes that work with the Java {@link java.util} package.
+ *
+ * @since 3.13.0
+ */
+package org.apache.commons.lang3.util;